@studiometa/trafic-agent 0.1.45 → 0.1.47
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 +129 -20
- package/dist/cli.js +49 -4
- package/dist/cli.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{server-BAjHxgqh.js → server-EPU5dtPx.js} +355 -21
- package/dist/server-EPU5dtPx.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -1
- package/dist/setup/agent.d.ts +2 -0
- package/dist/setup/agent.d.ts.map +1 -1
- package/dist/setup/audit.d.ts +39 -1
- package/dist/setup/audit.d.ts.map +1 -1
- package/dist/setup/ddev.d.ts +102 -3
- package/dist/setup/ddev.d.ts.map +1 -1
- package/dist/setup/docker.d.ts +23 -0
- package/dist/setup/docker.d.ts.map +1 -1
- package/dist/setup/index.d.ts.map +1 -1
- package/dist/setup/migrations/0015__catchall_priority.d.ts +40 -0
- package/dist/setup/migrations/0015__catchall_priority.d.ts.map +1 -0
- package/dist/setup/migrations/0016__wildcard_dns_challenge.d.ts +63 -0
- package/dist/setup/migrations/0016__wildcard_dns_challenge.d.ts.map +1 -0
- package/dist/setup/migrations/0018__docker_address_pools.d.ts +29 -0
- package/dist/setup/migrations/0018__docker_address_pools.d.ts.map +1 -0
- package/dist/setup/migrations/index.d.ts.map +1 -1
- package/dist/setup/types.d.ts +11 -0
- package/dist/setup/types.d.ts.map +1 -1
- package/dist/{setup-BrMbM8w4.js → setup-4zlNJqDe.js} +373 -173
- package/dist/setup-4zlNJqDe.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/tls.d.ts +10 -0
- package/dist/utils/tls.d.ts.map +1 -0
- package/package.json +1 -1
- package/templates/wait.html +1 -0
- package/dist/server-BAjHxgqh.js.map +0 -1
- package/dist/setup-BrMbM8w4.js.map +0 -1
|
@@ -1,120 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { B as resetSteps, C as shouldNeverStop, F as error, H as step, I as exec, L as execSilent, M as setProjectStatus, O as getIdleProjects, P as nodeIo, R as info, S as loadProjectConfig, U as success, V as setDryRun, W as warn, Y as loadProjectList, Z as stopProject, _ as installDdev, at as parseDuration, b as readToolPorts, d as TLS_STORE_CONFIG, f as addDdevAptRepo, g as getDockerGatewayIp, h as findRunningProject, it as loadConfig, l as ROUTER_COMPOSE_OVERRIDE, m as configureTraefik, p as configureDdev, q as getProjectInfo, u as STATIC_CONFIG, v as installSystemDeps, x as getIdleTimeoutMs, y as purgeTraefikAcmeStorage, z as isRoot } from "./server-EPU5dtPx.js";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { parse } from "smol-toml";
|
|
4
|
-
import { execSync } from "node:child_process";
|
|
5
|
-
//#region src/utils/config.ts
|
|
6
|
-
var DEFAULT_CONFIG_PATHS = [
|
|
7
|
-
"/etc/trafic/config.toml",
|
|
8
|
-
"./config.toml",
|
|
9
|
-
"./trafic.toml"
|
|
10
|
-
];
|
|
11
|
-
var DEFAULT_CONFIG = {
|
|
12
|
-
tld: "",
|
|
13
|
-
port: 9876,
|
|
14
|
-
dbPath: "/var/lib/trafic/db.sqlite",
|
|
15
|
-
projectListPath: "/home/ddev/.ddev/project_list.yaml",
|
|
16
|
-
projectsDir: "/home/ddev/www",
|
|
17
|
-
idleTimeout: "30m",
|
|
18
|
-
idleCheckInterval: "5m",
|
|
19
|
-
auth: {
|
|
20
|
-
defaultPolicy: "basic",
|
|
21
|
-
allowedIps: [],
|
|
22
|
-
tokens: [],
|
|
23
|
-
basicAuth: [],
|
|
24
|
-
rules: [],
|
|
25
|
-
trustedProxyHops: 1
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
/**
|
|
29
|
-
* Parse a duration string (e.g., "30m", "1h", "2h30m") to milliseconds
|
|
30
|
-
*/
|
|
31
|
-
function parseDuration(duration) {
|
|
32
|
-
const match = duration.match(/(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/);
|
|
33
|
-
if (!match) return 0;
|
|
34
|
-
const hours = parseInt(match[1] ?? "0", 10);
|
|
35
|
-
const minutes = parseInt(match[2] ?? "0", 10);
|
|
36
|
-
const seconds = parseInt(match[3] ?? "0", 10);
|
|
37
|
-
return (hours * 3600 + minutes * 60 + seconds) * 1e3;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Load configuration from TOML file
|
|
41
|
-
*/
|
|
42
|
-
function loadConfig(configPath) {
|
|
43
|
-
let path = configPath;
|
|
44
|
-
if (!path) path = DEFAULT_CONFIG_PATHS.find((p) => existsSync(p));
|
|
45
|
-
if (!path || !existsSync(path)) {
|
|
46
|
-
console.warn("No config file found, using defaults");
|
|
47
|
-
return DEFAULT_CONFIG;
|
|
48
|
-
}
|
|
49
|
-
const content = readFileSync(path, "utf-8");
|
|
50
|
-
const parsed = parse(content);
|
|
51
|
-
const auth = mergeAuthConfig(parsed.auth);
|
|
52
|
-
return {
|
|
53
|
-
tld: parsed.tld ?? DEFAULT_CONFIG.tld,
|
|
54
|
-
port: parsed.port ?? DEFAULT_CONFIG.port,
|
|
55
|
-
dbPath: parsed.db_path ?? DEFAULT_CONFIG.dbPath,
|
|
56
|
-
projectListPath: parsed.project_list_path ?? DEFAULT_CONFIG.projectListPath,
|
|
57
|
-
projectsDir: parsed.projects_dir ?? DEFAULT_CONFIG.projectsDir,
|
|
58
|
-
idleTimeout: parsed.idle_timeout ?? DEFAULT_CONFIG.idleTimeout,
|
|
59
|
-
idleCheckInterval: parsed.idle_check_interval ?? DEFAULT_CONFIG.idleCheckInterval,
|
|
60
|
-
auth
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Parse basic_auth entries from TOML.
|
|
65
|
-
* Accepts either plain strings ("user:pass") or objects ({username, password}).
|
|
66
|
-
*/
|
|
67
|
-
function parseBasicAuthEntries(raw) {
|
|
68
|
-
if (!Array.isArray(raw)) return null;
|
|
69
|
-
return raw.map((entry) => {
|
|
70
|
-
if (typeof entry === "string") return entry;
|
|
71
|
-
if (typeof entry === "object" && entry !== null) {
|
|
72
|
-
const { username, password } = entry;
|
|
73
|
-
return `${username}:${password}`;
|
|
74
|
-
}
|
|
75
|
-
return String(entry);
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
function mergeAuthConfig(raw) {
|
|
79
|
-
if (!raw) return DEFAULT_CONFIG.auth;
|
|
80
|
-
const rules = raw.rules ?? [];
|
|
81
|
-
return {
|
|
82
|
-
defaultPolicy: raw.default_policy ?? DEFAULT_CONFIG.auth.defaultPolicy,
|
|
83
|
-
allowedIps: raw.allowed_ips ?? DEFAULT_CONFIG.auth.allowedIps,
|
|
84
|
-
tokens: raw.tokens ?? DEFAULT_CONFIG.auth.tokens,
|
|
85
|
-
basicAuth: parseBasicAuthEntries(raw.basic_auth) ?? DEFAULT_CONFIG.auth.basicAuth,
|
|
86
|
-
rules: rules.map((r) => ({
|
|
87
|
-
match: r.match,
|
|
88
|
-
policy: r.policy,
|
|
89
|
-
tokens: r.tokens,
|
|
90
|
-
allowedIps: r.allowed_ips
|
|
91
|
-
})),
|
|
92
|
-
trustedProxyHops: parseTrustedProxyHops(raw.trusted_proxy_hops)
|
|
93
|
-
};
|
|
94
|
-
}
|
|
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
|
-
/**
|
|
108
|
-
* Validate configuration
|
|
109
|
-
*/
|
|
110
|
-
function validateConfig(config) {
|
|
111
|
-
const errors = [];
|
|
112
|
-
if (!config.tld) errors.push("tld is required");
|
|
113
|
-
if (config.port < 1 || config.port > 65535) errors.push("port must be between 1 and 65535");
|
|
114
|
-
if (config.auth.defaultPolicy !== "allow" && config.auth.defaultPolicy !== "deny" && config.auth.defaultPolicy !== "basic" && config.auth.defaultPolicy !== "token") errors.push("auth.default_policy must be \"allow\", \"deny\", \"basic\", or \"token\"");
|
|
115
|
-
return errors;
|
|
116
|
-
}
|
|
117
|
-
//#endregion
|
|
118
3
|
//#region src/setup/docker-firewall.ts
|
|
119
4
|
/** Where the rule script lives, so systemd and a human can both run it. */
|
|
120
5
|
var DOCKER_FIREWALL_SCRIPT = "/usr/local/sbin/trafic-docker-firewall";
|
|
@@ -341,6 +226,44 @@ function installDocker(io = nodeIo) {
|
|
|
341
226
|
io.exec("usermod -aG docker ddev", { silent: true });
|
|
342
227
|
success("Added ddev user to docker group");
|
|
343
228
|
}
|
|
229
|
+
/** Docker daemon config file */
|
|
230
|
+
var DAEMON_CONFIG = "/etc/docker/daemon.json";
|
|
231
|
+
/**
|
|
232
|
+
* Address pools for the networks Docker creates.
|
|
233
|
+
*
|
|
234
|
+
* Every DDEV project gets its own network. Docker's built-in pools hold only
|
|
235
|
+
* about 31 of them (15 × /16 in 172.17–31.x, 16 × /20 in 192.168.x), so a
|
|
236
|
+
* server with ~30 projects fails with "all predefined address pools have
|
|
237
|
+
* been fully subnetted". /24 subnets in 172.16.0.0/12 give 4096 networks,
|
|
238
|
+
* and keep every network inside the range the firewall rules already allow
|
|
239
|
+
* (hardening.ts, docker-firewall.ts) — the built-in 192.168.x pools are not.
|
|
240
|
+
*/
|
|
241
|
+
var DOCKER_ADDRESS_POOLS = [{
|
|
242
|
+
base: "172.16.0.0/12",
|
|
243
|
+
size: 24
|
|
244
|
+
}];
|
|
245
|
+
/**
|
|
246
|
+
* Add the address pools to an existing daemon config.
|
|
247
|
+
*
|
|
248
|
+
* Returns true when the config was changed. Leaves a config that already
|
|
249
|
+
* sets pools alone (an operator choice), and one it cannot parse.
|
|
250
|
+
*/
|
|
251
|
+
function addDockerAddressPools(io = nodeIo) {
|
|
252
|
+
let config = {};
|
|
253
|
+
if (io.fileExists("/etc/docker/daemon.json")) try {
|
|
254
|
+
config = JSON.parse(io.readFile(DAEMON_CONFIG));
|
|
255
|
+
} catch {
|
|
256
|
+
warn("Could not parse existing Docker config, skipping");
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
if ("default-address-pools" in config) return false;
|
|
260
|
+
io.exec("mkdir -p /etc/docker");
|
|
261
|
+
io.writeFile(DAEMON_CONFIG, JSON.stringify({
|
|
262
|
+
...config,
|
|
263
|
+
"default-address-pools": DOCKER_ADDRESS_POOLS
|
|
264
|
+
}, null, 2));
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
344
267
|
/**
|
|
345
268
|
* Configure Docker for production use
|
|
346
269
|
*/
|
|
@@ -353,10 +276,11 @@ function configureDocker(io = nodeIo) {
|
|
|
353
276
|
"max-file": "3"
|
|
354
277
|
},
|
|
355
278
|
"storage-driver": "overlay2",
|
|
356
|
-
"live-restore": true
|
|
279
|
+
"live-restore": true,
|
|
280
|
+
"default-address-pools": DOCKER_ADDRESS_POOLS
|
|
357
281
|
};
|
|
358
282
|
io.exec("mkdir -p /etc/docker");
|
|
359
|
-
const configPath =
|
|
283
|
+
const configPath = DAEMON_CONFIG;
|
|
360
284
|
if (io.fileExists(configPath)) try {
|
|
361
285
|
const merged = {
|
|
362
286
|
...JSON.parse(io.readFile(configPath)),
|
|
@@ -369,7 +293,7 @@ function configureDocker(io = nodeIo) {
|
|
|
369
293
|
return;
|
|
370
294
|
}
|
|
371
295
|
else io.writeFile(configPath, JSON.stringify(daemonConfig, null, 2));
|
|
372
|
-
io.exec("systemctl
|
|
296
|
+
io.exec("systemctl restart docker");
|
|
373
297
|
success("Docker configured with production settings");
|
|
374
298
|
}
|
|
375
299
|
/**
|
|
@@ -437,15 +361,21 @@ function installAgent(io = nodeIo) {
|
|
|
437
361
|
* Create the agent configuration
|
|
438
362
|
*/
|
|
439
363
|
function createAgentConfig(options, io = nodeIo) {
|
|
440
|
-
const { tld, trustedProxyHops = 1 } = options;
|
|
364
|
+
const { tld, trustedProxyHops = 1, dnsProvider, dnsEnv = {} } = options;
|
|
441
365
|
step("Create agent configuration");
|
|
442
366
|
io.exec("mkdir -p /etc/trafic");
|
|
443
367
|
io.exec("mkdir -p /var/lib/trafic");
|
|
444
368
|
io.exec("chown ddev:ddev /var/lib/trafic");
|
|
445
369
|
const configPath = "/etc/trafic/config.toml";
|
|
370
|
+
const tlsSection = buildTlsSection(dnsProvider, dnsEnv);
|
|
446
371
|
if (io.fileExists(configPath)) {
|
|
447
372
|
info("Configuration already exists at /etc/trafic/config.toml — skipping");
|
|
448
373
|
info("Edit this file to configure authentication");
|
|
374
|
+
if (tlsSection) {
|
|
375
|
+
info("Add this to /etc/trafic/config.toml, then run `trafic-agent upgrade`:");
|
|
376
|
+
for (const line of buildTlsSection(dnsProvider, dnsEnv, true).trim().split("\n")) info(` ${line}`);
|
|
377
|
+
info("Replace each REPLACE_ME with the value you passed to --dns-env.");
|
|
378
|
+
}
|
|
449
379
|
return;
|
|
450
380
|
}
|
|
451
381
|
const config = `# Trafic agent configuration
|
|
@@ -480,7 +410,7 @@ rules = []
|
|
|
480
410
|
# Too low and allowed_ips matches a proxy instead of the client; too high and
|
|
481
411
|
# it can be spoofed by a client-supplied header.
|
|
482
412
|
trusted_proxy_hops = ${trustedProxyHops}
|
|
483
|
-
`;
|
|
413
|
+
${tlsSection}`;
|
|
484
414
|
io.writeFile(configPath, config);
|
|
485
415
|
io.exec("chmod 640 /etc/trafic/config.toml");
|
|
486
416
|
io.exec("chown root:ddev /etc/trafic/config.toml");
|
|
@@ -488,6 +418,31 @@ trusted_proxy_hops = ${trustedProxyHops}
|
|
|
488
418
|
info("Edit this file to configure authentication");
|
|
489
419
|
}
|
|
490
420
|
/**
|
|
421
|
+
* Build the [tls] section of the generated config.
|
|
422
|
+
*
|
|
423
|
+
* Empty without a DNS provider: per-host certificates stay the default, and
|
|
424
|
+
* an empty section would only invite a half-filled one.
|
|
425
|
+
*/
|
|
426
|
+
function buildTlsSection(dnsProvider, dnsEnv, redact = false) {
|
|
427
|
+
if (!dnsProvider) return "";
|
|
428
|
+
const env = Object.entries(dnsEnv).map(([key, value]) => `${key} = "${redact ? "REPLACE_ME" : escapeTomlString(value)}"`).join("\n");
|
|
429
|
+
return `
|
|
430
|
+
# Wildcard certificate (DNS-01). One *.<tld> certificate replaces the
|
|
431
|
+
# per-hostname ones, so previews stop consuming the Let's Encrypt quota.
|
|
432
|
+
[tls]
|
|
433
|
+
dns_provider = "${escapeTomlString(dnsProvider)}"
|
|
434
|
+
# ca_server = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
|
435
|
+
|
|
436
|
+
# Credentials the lego provider reads. Passed to the ddev-router container.
|
|
437
|
+
[tls.dns_env]
|
|
438
|
+
${env}
|
|
439
|
+
`;
|
|
440
|
+
}
|
|
441
|
+
/** Escape a value for a basic (double-quoted) TOML string. */
|
|
442
|
+
function escapeTomlString(value) {
|
|
443
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
491
446
|
* Create systemd service for the agent
|
|
492
447
|
*/
|
|
493
448
|
function createSystemdService(io = nodeIo) {
|
|
@@ -811,32 +766,21 @@ function hardenServer(options = {}, io = nodeIo) {
|
|
|
811
766
|
//#endregion
|
|
812
767
|
//#region src/setup/audit.ts
|
|
813
768
|
/**
|
|
814
|
-
* Run a command silently and return output
|
|
815
|
-
*/
|
|
816
|
-
function execSilent(command) {
|
|
817
|
-
try {
|
|
818
|
-
return execSync(command, {
|
|
819
|
-
encoding: "utf-8",
|
|
820
|
-
stdio: "pipe"
|
|
821
|
-
}).trim();
|
|
822
|
-
} catch {
|
|
823
|
-
return "";
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
/**
|
|
827
769
|
* Check if a service is active
|
|
828
770
|
*/
|
|
829
|
-
function isServiceActive(service) {
|
|
830
|
-
return execSilent(`systemctl is-active ${service}`) === "active";
|
|
771
|
+
function isServiceActive(service, io) {
|
|
772
|
+
return io.execSilent(`systemctl is-active ${service}`) === "active";
|
|
773
|
+
}
|
|
774
|
+
/** Read a file, or "" when it does not exist. */
|
|
775
|
+
function readOrEmpty(path, io) {
|
|
776
|
+
return io.fileExists(path) ? io.readFile(path) : "";
|
|
831
777
|
}
|
|
832
778
|
/**
|
|
833
779
|
* Audit SSH configuration
|
|
834
780
|
*/
|
|
835
|
-
function auditSsh() {
|
|
781
|
+
function auditSsh(io = nodeIo) {
|
|
836
782
|
const checks = [];
|
|
837
|
-
const
|
|
838
|
-
const traficConfig = existsSync("/etc/ssh/sshd_config.d/trafic.conf") ? readFileSync("/etc/ssh/sshd_config.d/trafic.conf", "utf-8") : "";
|
|
839
|
-
const config = sshdConfig + "\n" + traficConfig;
|
|
783
|
+
const config = readOrEmpty("/etc/ssh/sshd_config", io) + "\n" + readOrEmpty("/etc/ssh/sshd_config.d/trafic.conf", io);
|
|
840
784
|
const rootLoginDisabled = config.includes("PermitRootLogin no") || config.includes("PermitRootLogin prohibit-password");
|
|
841
785
|
checks.push({
|
|
842
786
|
name: "SSH root login",
|
|
@@ -856,9 +800,9 @@ function auditSsh() {
|
|
|
856
800
|
/**
|
|
857
801
|
* Audit firewall configuration
|
|
858
802
|
*/
|
|
859
|
-
function auditFirewall() {
|
|
803
|
+
function auditFirewall(io = nodeIo) {
|
|
860
804
|
const checks = [];
|
|
861
|
-
const ufwStatus = execSilent("ufw status");
|
|
805
|
+
const ufwStatus = io.execSilent("ufw status");
|
|
862
806
|
const isActive = ufwStatus.includes("Status: active");
|
|
863
807
|
checks.push({
|
|
864
808
|
name: "Firewall (UFW)",
|
|
@@ -887,23 +831,23 @@ function auditFirewall() {
|
|
|
887
831
|
/**
|
|
888
832
|
* Audit security services
|
|
889
833
|
*/
|
|
890
|
-
function auditServices() {
|
|
834
|
+
function auditServices(io = nodeIo) {
|
|
891
835
|
const checks = [];
|
|
892
|
-
const fail2banActive = isServiceActive("fail2ban");
|
|
836
|
+
const fail2banActive = isServiceActive("fail2ban", io);
|
|
893
837
|
checks.push({
|
|
894
838
|
name: "Fail2ban",
|
|
895
839
|
status: fail2banActive ? "pass" : "warn",
|
|
896
840
|
message: fail2banActive ? "Fail2ban is active" : "Fail2ban is not running",
|
|
897
841
|
fix: "Run: apt install fail2ban && systemctl enable fail2ban --now"
|
|
898
842
|
});
|
|
899
|
-
const unattendedActive = isServiceActive("unattended-upgrades");
|
|
843
|
+
const unattendedActive = isServiceActive("unattended-upgrades", io);
|
|
900
844
|
checks.push({
|
|
901
845
|
name: "Automatic updates",
|
|
902
846
|
status: unattendedActive ? "pass" : "warn",
|
|
903
847
|
message: unattendedActive ? "Unattended upgrades enabled" : "Automatic updates not configured",
|
|
904
848
|
fix: "Run: apt install unattended-upgrades && dpkg-reconfigure unattended-upgrades"
|
|
905
849
|
});
|
|
906
|
-
const agentActive = isServiceActive("trafic-agent");
|
|
850
|
+
const agentActive = isServiceActive("trafic-agent", io);
|
|
907
851
|
checks.push({
|
|
908
852
|
name: "Trafic agent",
|
|
909
853
|
status: agentActive ? "pass" : "fail",
|
|
@@ -915,16 +859,16 @@ function auditServices() {
|
|
|
915
859
|
/**
|
|
916
860
|
* Audit Docker
|
|
917
861
|
*/
|
|
918
|
-
function auditDocker() {
|
|
862
|
+
function auditDocker(io = nodeIo) {
|
|
919
863
|
const checks = [];
|
|
920
|
-
const dockerActive = isServiceActive("docker");
|
|
864
|
+
const dockerActive = isServiceActive("docker", io);
|
|
921
865
|
checks.push({
|
|
922
866
|
name: "Docker",
|
|
923
867
|
status: dockerActive ? "pass" : "fail",
|
|
924
868
|
message: dockerActive ? "Docker is running" : "Docker is not running",
|
|
925
869
|
fix: "Run: systemctl start docker"
|
|
926
870
|
});
|
|
927
|
-
const danglingImages = execSilent("docker images -f dangling=true -q | wc -l");
|
|
871
|
+
const danglingImages = io.execSilent("docker images -f dangling=true -q | wc -l");
|
|
928
872
|
const count = parseInt(danglingImages, 10) || 0;
|
|
929
873
|
if (count > 0) checks.push({
|
|
930
874
|
name: "Docker cleanup",
|
|
@@ -932,7 +876,7 @@ function auditDocker() {
|
|
|
932
876
|
message: `${count} dangling Docker images`,
|
|
933
877
|
fix: "Run: docker system prune -f"
|
|
934
878
|
});
|
|
935
|
-
const diskUsage = execSilent("df -h / | awk 'NR==2{print $5}'");
|
|
879
|
+
const diskUsage = io.execSilent("df -h / | awk 'NR==2{print $5}'");
|
|
936
880
|
const usagePercent = parseInt(diskUsage, 10) || 0;
|
|
937
881
|
checks.push({
|
|
938
882
|
name: "Disk usage",
|
|
@@ -945,10 +889,10 @@ function auditDocker() {
|
|
|
945
889
|
/**
|
|
946
890
|
* Audit file permissions
|
|
947
891
|
*/
|
|
948
|
-
function auditPermissions() {
|
|
892
|
+
function auditPermissions(io = nodeIo) {
|
|
949
893
|
const checks = [];
|
|
950
|
-
if (
|
|
951
|
-
const stats = execSilent("stat -c '%a' /etc/trafic/config.toml");
|
|
894
|
+
if (io.fileExists("/etc/trafic/config.toml")) {
|
|
895
|
+
const stats = io.execSilent("stat -c '%a' /etc/trafic/config.toml");
|
|
952
896
|
const isSecure = stats === "640" || stats === "600";
|
|
953
897
|
checks.push({
|
|
954
898
|
name: "Config permissions",
|
|
@@ -957,8 +901,8 @@ function auditPermissions() {
|
|
|
957
901
|
fix: "Run: chmod 640 /etc/trafic/config.toml"
|
|
958
902
|
});
|
|
959
903
|
}
|
|
960
|
-
if (
|
|
961
|
-
const owner = execSilent("stat -c '%U:%G' /home/ddev/www");
|
|
904
|
+
if (io.fileExists("/home/ddev/www")) {
|
|
905
|
+
const owner = io.execSilent("stat -c '%U:%G' /home/ddev/www");
|
|
962
906
|
const isCorrect = owner === "ddev:ddev";
|
|
963
907
|
checks.push({
|
|
964
908
|
name: "Projects directory",
|
|
@@ -970,15 +914,86 @@ function auditPermissions() {
|
|
|
970
914
|
return checks;
|
|
971
915
|
}
|
|
972
916
|
/**
|
|
917
|
+
* Audit the wildcard certificate, where one is configured.
|
|
918
|
+
*
|
|
919
|
+
* Two things go wrong silently: the certificate is never issued (a wrong
|
|
920
|
+
* token, a zone the token cannot edit), and the token file ends up readable
|
|
921
|
+
* by everyone.
|
|
922
|
+
*/
|
|
923
|
+
function auditWildcardTls(config, io = nodeIo) {
|
|
924
|
+
if (!config.tls.dnsProvider) return [];
|
|
925
|
+
const checks = [];
|
|
926
|
+
checks.push(wildcardCertificateCheck(config.tld, io));
|
|
927
|
+
if (io.fileExists("/home/ddev/.ddev/router-compose.trafic.yaml")) {
|
|
928
|
+
const mode = io.execSilent(`stat -c '%a' ${ROUTER_COMPOSE_OVERRIDE}`);
|
|
929
|
+
const isSecure = mode === "600";
|
|
930
|
+
checks.push({
|
|
931
|
+
name: "DNS credentials permissions",
|
|
932
|
+
status: isSecure ? "pass" : "warn",
|
|
933
|
+
message: isSecure ? "Router compose override is readable by its owner only" : `Router compose override permissions: ${mode} (should be 600)`,
|
|
934
|
+
fix: `Run: chmod 600 ${ROUTER_COMPOSE_OVERRIDE}`
|
|
935
|
+
});
|
|
936
|
+
}
|
|
937
|
+
return checks;
|
|
938
|
+
}
|
|
939
|
+
/** Look for the wildcard certificate in Traefik's DNS-01 ACME storage. */
|
|
940
|
+
function wildcardCertificateCheck(tld, io) {
|
|
941
|
+
const fix = "Check the router log for ACME errors: docker logs ddev-router 2>&1 | grep -i acme";
|
|
942
|
+
const mountpoint = io.execSilent("docker volume inspect ddev-global-cache --format '{{.Mountpoint}}'");
|
|
943
|
+
if (!mountpoint) return {
|
|
944
|
+
name: "Wildcard certificate",
|
|
945
|
+
status: "warn",
|
|
946
|
+
message: "Traefik's storage volume (ddev-global-cache) was not found",
|
|
947
|
+
fix
|
|
948
|
+
};
|
|
949
|
+
const storage = `${mountpoint}/traefik/acme-dns.json`;
|
|
950
|
+
if (!io.fileExists(storage)) return {
|
|
951
|
+
name: "Wildcard certificate",
|
|
952
|
+
status: "warn",
|
|
953
|
+
message: "No DNS-01 certificate has been issued yet",
|
|
954
|
+
fix
|
|
955
|
+
};
|
|
956
|
+
const issued = hasCertificateFor(io.readFile(storage), tld);
|
|
957
|
+
return {
|
|
958
|
+
name: "Wildcard certificate",
|
|
959
|
+
status: issued ? "pass" : "warn",
|
|
960
|
+
message: issued ? `Wildcard certificate issued for *.${tld}` : `No certificate for ${tld} in Traefik's DNS-01 storage`,
|
|
961
|
+
fix: issued ? void 0 : fix
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Whether Traefik's ACME storage holds a certificate whose main domain is the
|
|
966
|
+
* TLD — which is the wildcard one, since that is what the default store asks
|
|
967
|
+
* for.
|
|
968
|
+
*
|
|
969
|
+
* The file is keyed by resolver name, each with a Certificates array.
|
|
970
|
+
*/
|
|
971
|
+
function hasCertificateFor(storage, tld) {
|
|
972
|
+
let parsed;
|
|
973
|
+
try {
|
|
974
|
+
parsed = JSON.parse(storage);
|
|
975
|
+
} catch {
|
|
976
|
+
return false;
|
|
977
|
+
}
|
|
978
|
+
if (parsed === null || typeof parsed !== "object") return false;
|
|
979
|
+
for (const resolver of Object.values(parsed)) {
|
|
980
|
+
const certificates = resolver.Certificates;
|
|
981
|
+
if (!Array.isArray(certificates)) continue;
|
|
982
|
+
for (const certificate of certificates) if (certificate.domain?.main === tld) return true;
|
|
983
|
+
}
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
973
987
|
* Run all audit checks
|
|
974
988
|
*/
|
|
975
|
-
function runAudit() {
|
|
989
|
+
function runAudit(io = nodeIo, config = loadConfig()) {
|
|
976
990
|
return [
|
|
977
|
-
...auditSsh(),
|
|
978
|
-
...auditFirewall(),
|
|
979
|
-
...auditServices(),
|
|
980
|
-
...auditDocker(),
|
|
981
|
-
...auditPermissions()
|
|
991
|
+
...auditSsh(io),
|
|
992
|
+
...auditFirewall(io),
|
|
993
|
+
...auditServices(io),
|
|
994
|
+
...auditDocker(io),
|
|
995
|
+
...auditPermissions(io),
|
|
996
|
+
...auditWildcardTls(config, io)
|
|
982
997
|
];
|
|
983
998
|
}
|
|
984
999
|
/**
|
|
@@ -1157,7 +1172,7 @@ var migration0007UfwDockerTraficPort = {
|
|
|
1157
1172
|
id: "0007__ufw_docker_trafic_port",
|
|
1158
1173
|
description: "Allow trafic-agent port 9876 from Docker bridge subnets in UFW",
|
|
1159
1174
|
run() {
|
|
1160
|
-
if (execSilent
|
|
1175
|
+
if (execSilent("ufw status | grep '9876'").includes("9876")) return;
|
|
1161
1176
|
exec("ufw allow from 172.16.0.0/12 to any port 9876 comment 'trafic-agent from Docker'", { silent: true });
|
|
1162
1177
|
}
|
|
1163
1178
|
};
|
|
@@ -1234,7 +1249,7 @@ http:
|
|
|
1234
1249
|
//#endregion
|
|
1235
1250
|
//#region src/setup/migrations/0009__scale_to_zero_fixes.ts
|
|
1236
1251
|
var UNIT_PATH = "/etc/systemd/system/trafic-agent.service";
|
|
1237
|
-
var DYNAMIC_CONFIG$
|
|
1252
|
+
var DYNAMIC_CONFIG$2 = "/home/ddev/.ddev/traefik/trafic.yaml";
|
|
1238
1253
|
/**
|
|
1239
1254
|
* Migration 0009: Make scale-to-zero actually work.
|
|
1240
1255
|
*
|
|
@@ -1261,7 +1276,7 @@ var migration0009ScaleToZeroFixes = {
|
|
|
1261
1276
|
description: "Fix scale-to-zero: waiting page router and agent write access",
|
|
1262
1277
|
run() {
|
|
1263
1278
|
const unitNeedsWrite = !existsSync(UNIT_PATH) || !/^ReadWritePaths=.*\/home\/ddev(\s|$)/m.test(readFileSync(UNIT_PATH, "utf-8"));
|
|
1264
|
-
if (!existsSync(DYNAMIC_CONFIG$
|
|
1279
|
+
if (!existsSync(DYNAMIC_CONFIG$2) || !readFileSync(DYNAMIC_CONFIG$2, "utf-8").includes("trafic-catchall")) {
|
|
1265
1280
|
configureTraefik();
|
|
1266
1281
|
exec("su - ddev -c 'DDEV_NONINTERACTIVE=true ddev poweroff && ddev start --all'", { silent: true });
|
|
1267
1282
|
}
|
|
@@ -1270,7 +1285,7 @@ var migration0009ScaleToZeroFixes = {
|
|
|
1270
1285
|
};
|
|
1271
1286
|
//#endregion
|
|
1272
1287
|
//#region src/setup/migrations/0010__catchall_entrypoints.ts
|
|
1273
|
-
var DYNAMIC_CONFIG = "/home/ddev/.ddev/traefik/custom-global-config/trafic.yaml";
|
|
1288
|
+
var DYNAMIC_CONFIG$1 = "/home/ddev/.ddev/traefik/custom-global-config/trafic.yaml";
|
|
1274
1289
|
/** The copy setup used to write for DDEV versions that watched the root. */
|
|
1275
1290
|
var STALE_CONFIG = "/home/ddev/.ddev/traefik/trafic.yaml";
|
|
1276
1291
|
/**
|
|
@@ -1297,7 +1312,7 @@ var migration0010CatchallEntrypoints = {
|
|
|
1297
1312
|
id: "0010__catchall_entrypoints",
|
|
1298
1313
|
description: "Pin the Traefik catch-all routers to their entry points",
|
|
1299
1314
|
run() {
|
|
1300
|
-
const pinned = existsSync(DYNAMIC_CONFIG) && readFileSync(DYNAMIC_CONFIG, "utf-8").includes("entryPoints:");
|
|
1315
|
+
const pinned = existsSync(DYNAMIC_CONFIG$1) && readFileSync(DYNAMIC_CONFIG$1, "utf-8").includes("entryPoints:");
|
|
1301
1316
|
if (pinned && !existsSync(STALE_CONFIG)) return;
|
|
1302
1317
|
if (!pinned) configureTraefik();
|
|
1303
1318
|
if (existsSync(STALE_CONFIG)) exec(`rm -f ${STALE_CONFIG}`, { silent: true });
|
|
@@ -1332,10 +1347,10 @@ var migration0011PurgeAcmeWhenDisabled = {
|
|
|
1332
1347
|
id: "0011__purge_acme_when_disabled",
|
|
1333
1348
|
description: "Remove Traefik's Let's Encrypt storage when it is disabled",
|
|
1334
1349
|
run() {
|
|
1335
|
-
const globalConfig = execSilent
|
|
1350
|
+
const globalConfig = execSilent("su - ddev -c 'ddev config global'");
|
|
1336
1351
|
if (!/^use-letsencrypt=false$/m.test(globalConfig)) return;
|
|
1337
1352
|
if (!purgeTraefikAcmeStorage()) return;
|
|
1338
|
-
execSilent
|
|
1353
|
+
execSilent("docker restart ddev-router");
|
|
1339
1354
|
}
|
|
1340
1355
|
};
|
|
1341
1356
|
//#endregion
|
|
@@ -1436,6 +1451,178 @@ var migration0014UnattendedUpgradesDockerOrigin = {
|
|
|
1436
1451
|
}
|
|
1437
1452
|
};
|
|
1438
1453
|
//#endregion
|
|
1454
|
+
//#region src/setup/migrations/0015__catchall_priority.ts
|
|
1455
|
+
var DYNAMIC_CONFIG = "/home/ddev/.ddev/traefik/custom-global-config/trafic.yaml";
|
|
1456
|
+
/**
|
|
1457
|
+
* Migration 0015: raise the Trafic catch-all routers above DDEV's own.
|
|
1458
|
+
*
|
|
1459
|
+
* DDEV 1.25.4's router image adds two catch-all routers of its own in
|
|
1460
|
+
* `default_config.yaml` — `ddev-router-fallback-http` and
|
|
1461
|
+
* `ddev-router-fallback-https`, from the `traefik_global_config_template.yaml`
|
|
1462
|
+
* template in ddev/ddev. They carry the rule `PathPrefix(`/`)` at priority 1,
|
|
1463
|
+
* on every router entry point, and point at a socat responder that answers
|
|
1464
|
+
* 404. Trafic's own catch-all routers used priority 1 too.
|
|
1465
|
+
*
|
|
1466
|
+
* With equal priority and identical rules Traefik picked DDEV's, so a stopped
|
|
1467
|
+
* project answered DDEV's 404 page instead of the Trafic waiting page, and
|
|
1468
|
+
* scale-to-zero never restarted it — the waiting page is what triggers the
|
|
1469
|
+
* restart. Observed in the access log of a live server:
|
|
1470
|
+
* `"GET /rien-a-mettre.html HTTP/2.0" 404 ...
|
|
1471
|
+
* "http-443-ddev-router-fallback-https@file"`.
|
|
1472
|
+
*
|
|
1473
|
+
* `trafic-agent upgrade` installs DDEV 1.25.4, so every upgraded server is
|
|
1474
|
+
* affected. Priority 2 puts the Trafic routers back in front, and is still far
|
|
1475
|
+
* below any project router, so per-project routing and auth are unchanged.
|
|
1476
|
+
*
|
|
1477
|
+
* Traefik reads its config from the ddev-global-cache volume, and DDEV copies
|
|
1478
|
+
* `~/.ddev/traefik/custom-global-config/*.yaml` into it on any `ddev start` —
|
|
1479
|
+
* `PushGlobalTraefikConfig` runs even when the router is already up. Starting
|
|
1480
|
+
* one project that is already running is therefore enough to load the new
|
|
1481
|
+
* priority; `ddev poweroff` would stop every preview on the server for no
|
|
1482
|
+
* gain, so it is not used. When nothing runs, there is nothing to push to and
|
|
1483
|
+
* the next project start applies it.
|
|
1484
|
+
*
|
|
1485
|
+
* Unrelated and not addressed here: DDEV 1.25.4 also logs "Router count
|
|
1486
|
+
* mismatch" warnings, because its two fallback definitions attach to every
|
|
1487
|
+
* entry point while its health check counts definitions. That is DDEV's own
|
|
1488
|
+
* check against DDEV's own routers.
|
|
1489
|
+
*
|
|
1490
|
+
* Idempotent: skipped when the config is missing, and when no `priority: 1`
|
|
1491
|
+
* is left in it.
|
|
1492
|
+
*/
|
|
1493
|
+
var migration0015CatchallPriority = {
|
|
1494
|
+
id: "0015__catchall_priority",
|
|
1495
|
+
description: "Raise the Traefik catch-all routers above DDEV's fallback routers",
|
|
1496
|
+
run() {
|
|
1497
|
+
if (!existsSync(DYNAMIC_CONFIG)) return;
|
|
1498
|
+
if (!readFileSync(DYNAMIC_CONFIG, "utf-8").includes("priority: 1")) return;
|
|
1499
|
+
configureTraefik();
|
|
1500
|
+
const project = findRunningProject();
|
|
1501
|
+
if (!project) {
|
|
1502
|
+
info("No project is running — the new priority applies on the next project start.");
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
exec(`su - ddev -c 'DDEV_NONINTERACTIVE=true ddev start ${project}'`, { silent: true });
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
//#endregion
|
|
1509
|
+
//#region src/setup/migrations/0016__wildcard_dns_challenge.ts
|
|
1510
|
+
/**
|
|
1511
|
+
* Migration 0016: apply the wildcard DNS-01 certificate on servers that ask
|
|
1512
|
+
* for one in their config.
|
|
1513
|
+
*
|
|
1514
|
+
* `tls.dns_provider` is read by `configureTraefik`, which setup runs on a
|
|
1515
|
+
* fresh server. An existing server has the config but none of the files, so
|
|
1516
|
+
* this writes them: the resolver in `static_config.trafic.yaml`, the default
|
|
1517
|
+
* TLS store in `custom-global-config/0-trafic-tls.yaml`, and the provider
|
|
1518
|
+
* credentials in `router-compose.trafic.yaml`.
|
|
1519
|
+
*
|
|
1520
|
+
* Why the router is removed and a project started, rather than restarted
|
|
1521
|
+
* (the 0010 rationale, plus one more): Traefik reads its configuration from
|
|
1522
|
+
* the ddev-global-cache volume, and DDEV only copies `~/.ddev/traefik` into
|
|
1523
|
+
* that volume and merges `static_config.*.yaml` into `.static_config.yaml`
|
|
1524
|
+
* when a project starts. And DDEV 1.25 reads `router-compose.*.yaml` only
|
|
1525
|
+
* when it *recreates* the router: a running, healthy router just gets the
|
|
1526
|
+
* Traefik config pushed (StartDdevRouter, `needsRecreation`). Seen on a live
|
|
1527
|
+
* server — the static config was merged, the credentials never reached the
|
|
1528
|
+
* container. Removing the router first forces the recreation; `ddev start`
|
|
1529
|
+
* brings it back within seconds with the new environment.
|
|
1530
|
+
*
|
|
1531
|
+
* The container is found by its compose label, not by name: when compose
|
|
1532
|
+
* recreates a container it renames the old one `<id>_ddev-router`, and a
|
|
1533
|
+
* failed recreation can leave it that way. `docker restart ddev-router` then
|
|
1534
|
+
* silently does nothing — also seen on that server.
|
|
1535
|
+
*
|
|
1536
|
+
* Two details this depends on:
|
|
1537
|
+
* - Traefik's file provider keeps the FIRST `tls.stores.default` it reads in
|
|
1538
|
+
* directory order and logs "TLS store default already configured,
|
|
1539
|
+
* skipping" for the rest. DDEV writes an empty one in
|
|
1540
|
+
* `default_config.yaml`, so the agent's file is named `0-trafic-tls.yaml`
|
|
1541
|
+
* to sort ahead of it.
|
|
1542
|
+
* - DDEV keeps `certResolver: acme-tlsChallenge` on every project router, and
|
|
1543
|
+
* that is fine: Traefik skips a per-host ACME request once the default
|
|
1544
|
+
* store already holds a certificate matching the host with wildcard
|
|
1545
|
+
* semantics. So `use_letsencrypt` stays true — the ACME account email comes
|
|
1546
|
+
* from it — and the per-host quota use stops on its own.
|
|
1547
|
+
*
|
|
1548
|
+
* `ddev poweroff` is deliberately not used: the server runs live previews and
|
|
1549
|
+
* a project start is enough to regenerate the config and recreate the router.
|
|
1550
|
+
*
|
|
1551
|
+
* Idempotent: skipped when no DNS provider is configured, and when all three
|
|
1552
|
+
* files are already in place.
|
|
1553
|
+
*/
|
|
1554
|
+
var migration0016WildcardDnsChallenge = {
|
|
1555
|
+
id: "0016__wildcard_dns_challenge",
|
|
1556
|
+
description: "Apply the wildcard DNS-01 certificate where one is configured",
|
|
1557
|
+
run() {
|
|
1558
|
+
runWildcardMigration();
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
/**
|
|
1562
|
+
* The migration body, with its effects injected so tests can drive it.
|
|
1563
|
+
*
|
|
1564
|
+
* `tls` defaults to the server's own config: the migration runs after setup
|
|
1565
|
+
* wrote /etc/trafic/config.toml, so that file is the only source.
|
|
1566
|
+
*/
|
|
1567
|
+
function runWildcardMigration(io = nodeIo, tls = loadConfig().tls) {
|
|
1568
|
+
if (!tls.dnsProvider) return;
|
|
1569
|
+
if (io.fileExists(STATIC_CONFIG) && io.readFile(STATIC_CONFIG).includes(`acme-dns:`) && io.fileExists(TLS_STORE_CONFIG) && io.fileExists("/home/ddev/.ddev/router-compose.trafic.yaml")) return;
|
|
1570
|
+
configureTraefik({ tls }, io);
|
|
1571
|
+
const project = findRunningProject(io);
|
|
1572
|
+
if (!project) {
|
|
1573
|
+
info("No running project — the next deploy applies the wildcard certificate");
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
const router = findRouterContainer(io);
|
|
1577
|
+
if (router) io.execSilent(`docker rm -f ${router}`);
|
|
1578
|
+
io.exec(`su - ddev -c 'DDEV_NONINTERACTIVE=true ddev start ${project}'`, { silent: true });
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* The ddev-router container id, whatever its current name.
|
|
1582
|
+
*
|
|
1583
|
+
* Compose labels survive the `<id>_ddev-router` rename a recreation leaves
|
|
1584
|
+
* behind; the container name does not.
|
|
1585
|
+
*/
|
|
1586
|
+
function findRouterContainer(io = nodeIo) {
|
|
1587
|
+
return io.execSilent("docker ps -aq --filter label=com.docker.compose.service=ddev-router").trim().split("\n")[0] || void 0;
|
|
1588
|
+
}
|
|
1589
|
+
//#endregion
|
|
1590
|
+
//#region src/setup/migrations/0018__docker_address_pools.ts
|
|
1591
|
+
/**
|
|
1592
|
+
* Migration 0018: give Docker enough address space for one network per
|
|
1593
|
+
* project.
|
|
1594
|
+
*
|
|
1595
|
+
* `setup` wrote a daemon.json without `default-address-pools`, so Docker
|
|
1596
|
+
* used its built-in pools: about 31 networks. Each DDEV project creates its
|
|
1597
|
+
* own `ddev-<name>_default` network, and a server with ~30 projects failed
|
|
1598
|
+
* to start the next one with "all predefined address pools have been fully
|
|
1599
|
+
* subnetted". The last 16 of those built-in networks are in 192.168.x,
|
|
1600
|
+
* outside the 172.16.0.0/12 range the firewall allows to reach the agent.
|
|
1601
|
+
*
|
|
1602
|
+
* `configureDocker` now writes /24 pools in 172.16.0.0/12, which covers
|
|
1603
|
+
* fresh installs. This migration adds them to existing servers.
|
|
1604
|
+
*
|
|
1605
|
+
* Docker reads the pools only at start, so the daemon is restarted.
|
|
1606
|
+
* `live-restore`, set by setup, keeps the running containers up. Existing
|
|
1607
|
+
* networks keep their subnets; only new networks get a /24, and Docker skips
|
|
1608
|
+
* subnets an existing network already uses.
|
|
1609
|
+
*
|
|
1610
|
+
* Idempotent: does nothing when the config already sets pools.
|
|
1611
|
+
*/
|
|
1612
|
+
var migration0018DockerAddressPools = {
|
|
1613
|
+
id: "0018__docker_address_pools",
|
|
1614
|
+
description: "Give Docker address pools for one network per project",
|
|
1615
|
+
run() {
|
|
1616
|
+
runDockerAddressPoolsMigration();
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
/**
|
|
1620
|
+
* The migration body, with its effects injected so tests can drive it.
|
|
1621
|
+
*/
|
|
1622
|
+
function runDockerAddressPoolsMigration(io = nodeIo) {
|
|
1623
|
+
if (addDockerAddressPools(io)) io.exec("systemctl restart docker");
|
|
1624
|
+
}
|
|
1625
|
+
//#endregion
|
|
1439
1626
|
//#region src/setup/migrations/index.ts
|
|
1440
1627
|
/** Path to the persisted migration state file */
|
|
1441
1628
|
var MIGRATIONS_STATE_FILE = "/etc/trafic/.migrations.json";
|
|
@@ -1454,7 +1641,10 @@ var ALL_MIGRATIONS = [
|
|
|
1454
1641
|
migration0011PurgeAcmeWhenDisabled,
|
|
1455
1642
|
migration0012DockerUserToolPorts,
|
|
1456
1643
|
migration0013SshKexDefaults,
|
|
1457
|
-
migration0014UnattendedUpgradesDockerOrigin
|
|
1644
|
+
migration0014UnattendedUpgradesDockerOrigin,
|
|
1645
|
+
migration0015CatchallPriority,
|
|
1646
|
+
migration0016WildcardDnsChallenge,
|
|
1647
|
+
migration0018DockerAddressPools
|
|
1458
1648
|
];
|
|
1459
1649
|
/**
|
|
1460
1650
|
* Load the current migration state from disk.
|
|
@@ -1541,6 +1731,7 @@ async function setup(options) {
|
|
|
1541
1731
|
}
|
|
1542
1732
|
info(`TLD: ${options.tld}`);
|
|
1543
1733
|
if (options.email) info(`Let's Encrypt email: ${options.email}`);
|
|
1734
|
+
if (options.dnsProvider) info(`Wildcard certificate: DNS-01 via ${options.dnsProvider}`);
|
|
1544
1735
|
if (options.noHardening) info("Hardening: disabled");
|
|
1545
1736
|
if (options.noRootSsh) info("Root SSH login: disabled");
|
|
1546
1737
|
try {
|
|
@@ -1557,7 +1748,14 @@ async function setup(options) {
|
|
|
1557
1748
|
if (!options.noDdev) {
|
|
1558
1749
|
installDdev();
|
|
1559
1750
|
configureDdev(options.tld, options.email);
|
|
1560
|
-
configureTraefik(
|
|
1751
|
+
configureTraefik({
|
|
1752
|
+
tld: options.tld,
|
|
1753
|
+
tls: {
|
|
1754
|
+
dnsProvider: options.dnsProvider,
|
|
1755
|
+
caServer: options.caServer,
|
|
1756
|
+
dnsEnv: options.dnsEnv ?? {}
|
|
1757
|
+
}
|
|
1758
|
+
});
|
|
1561
1759
|
configureDockerFirewall(readToolPorts());
|
|
1562
1760
|
} else {
|
|
1563
1761
|
step("Install DDEV");
|
|
@@ -1567,7 +1765,9 @@ async function setup(options) {
|
|
|
1567
1765
|
installAgent();
|
|
1568
1766
|
createAgentConfig({
|
|
1569
1767
|
tld: options.tld,
|
|
1570
|
-
trustedProxyHops: options.trustedProxyHops
|
|
1768
|
+
trustedProxyHops: options.trustedProxyHops,
|
|
1769
|
+
dnsProvider: options.dnsProvider,
|
|
1770
|
+
dnsEnv: options.dnsEnv
|
|
1571
1771
|
});
|
|
1572
1772
|
createSystemdService();
|
|
1573
1773
|
if (!options.noHardening) hardenServer({
|
|
@@ -1600,6 +1800,6 @@ async function audit() {
|
|
|
1600
1800
|
if (checks.filter((c) => c.status === "fail").length > 0) process.exit(1);
|
|
1601
1801
|
}
|
|
1602
1802
|
//#endregion
|
|
1603
|
-
export { printAuditResults as a, stopIdleProjects as c, buildDockerFirewallScript as d, buildDockerFirewallUnit as f,
|
|
1803
|
+
export { printAuditResults as a, stopIdleProjects as c, buildDockerFirewallScript as d, buildDockerFirewallUnit as f, runPendingMigrations as i, DOCKER_FIREWALL_SCRIPT as l, setup as n, runAudit as o, configureDockerFirewall as p, listMigrations as r, startIdleScheduler as s, audit as t, DOCKER_FIREWALL_UNIT as u };
|
|
1604
1804
|
|
|
1605
|
-
//# sourceMappingURL=setup-
|
|
1805
|
+
//# sourceMappingURL=setup-4zlNJqDe.js.map
|