@studiometa/trafic-agent 0.1.36 → 0.1.38
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 +106 -4
- package/dist/cli.js +4 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/{server-4J8XoTP3.js → server-CvSztHW2.js} +127 -153
- package/dist/{server-4J8XoTP3.js.map → server-CvSztHW2.js.map} +1 -1
- package/dist/server.d.ts +0 -9
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -2
- package/dist/setup/docker-firewall.d.ts +35 -0
- package/dist/setup/docker-firewall.d.ts.map +1 -0
- package/dist/setup/hardening.d.ts.map +1 -1
- package/dist/setup/index.d.ts.map +1 -1
- package/dist/setup/migrations/0012__docker_user_tool_ports.d.ts +26 -0
- package/dist/setup/migrations/0012__docker_user_tool_ports.d.ts.map +1 -0
- package/dist/setup/migrations/index.d.ts.map +1 -1
- package/dist/{setup-ClYjldGS.js → setup-BFU-DlX3.js} +166 -4
- package/dist/setup-BFU-DlX3.js.map +1 -0
- package/package.json +1 -1
- package/dist/setup-ClYjldGS.js.map +0 -1
package/README.md
CHANGED
|
@@ -157,12 +157,114 @@ The agent:
|
|
|
157
157
|
|
|
158
158
|
## Endpoints
|
|
159
159
|
|
|
160
|
+
All internal endpoints are prefixed with `__` so they cannot collide with a
|
|
161
|
+
project's own paths. Everything else is treated as a request for a project.
|
|
162
|
+
|
|
160
163
|
| Endpoint | Description |
|
|
161
164
|
|----------|-------------|
|
|
162
|
-
| `GET /
|
|
163
|
-
| `GET /
|
|
164
|
-
| `GET /
|
|
165
|
-
|
|
|
165
|
+
| `GET /__auth__` | Forward auth for Traefik. `200` allows, `401` prompts for basic auth |
|
|
166
|
+
| `GET /__status__?project=<name>` | Project status as JSON, for the waiting page to poll |
|
|
167
|
+
| `GET /__health__` | Agent health and version |
|
|
168
|
+
| anything else | Waiting page for a known project, error page otherwise |
|
|
169
|
+
|
|
170
|
+
## TLS
|
|
171
|
+
|
|
172
|
+
Traefik obtains the certificates, and `setup --email` is what turns that on:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
trafic setup --host server.example.com --tld previews.example.com \
|
|
176
|
+
--email admin@example.com
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
With an email, DDEV sets `use_letsencrypt=true` and Traefik requests a
|
|
180
|
+
certificate per project hostname on first use — previews included, since each
|
|
181
|
+
one gets its own router. Without an email, projects are served with a locally
|
|
182
|
+
trusted mkcert certificate, which is fine behind a proxy that terminates TLS
|
|
183
|
+
itself but shows a browser warning if used directly.
|
|
184
|
+
|
|
185
|
+
Two limits worth knowing before you rely on it:
|
|
186
|
+
|
|
187
|
+
- Let's Encrypt allows **50 new certificates per registered domain per week**.
|
|
188
|
+
Each preview hostname is a new certificate, so environments that churn fast
|
|
189
|
+
can hit the ceiling. Renewals do not count against it.
|
|
190
|
+
- Turning Let's Encrypt off does not discard certificates already issued.
|
|
191
|
+
Traefik keeps them in `acme.json` in the `ddev-global-cache` volume and
|
|
192
|
+
serves them again after a restart, so `configureDdev` deletes that storage
|
|
193
|
+
when Let's Encrypt is disabled.
|
|
194
|
+
|
|
195
|
+
Wildcard certificates would avoid the per-hostname limit but need a DNS-01
|
|
196
|
+
challenge, which DDEV does not do. If you churn tens of previews a week, put a
|
|
197
|
+
proxy in front that can (Caddy or Traefik with a DNS provider) and disable
|
|
198
|
+
Let's Encrypt in DDEV so the two do not both try.
|
|
199
|
+
|
|
200
|
+
## Network exposure
|
|
201
|
+
|
|
202
|
+
**`ufw default deny incoming` does not cover the ports Docker publishes.**
|
|
203
|
+
Docker adds its rules to `nat/PREROUTING` and the `DOCKER` chain of `FORWARD`,
|
|
204
|
+
both of which are evaluated before UFW's chains. So the ports ddev-router
|
|
205
|
+
publishes are reachable from the internet whatever UFW says about them, and
|
|
206
|
+
`ufw status` will not tell you otherwise.
|
|
207
|
+
|
|
208
|
+
`setup` opens 22, 80 and 443, and 9876 for the agent from the Docker bridge
|
|
209
|
+
only. Those rules are accurate. What they do **not** do is close the ports
|
|
210
|
+
ddev-router publishes for DDEV's tools:
|
|
211
|
+
|
|
212
|
+
| Port | Service |
|
|
213
|
+
|------|---------|
|
|
214
|
+
| 8025, 8026 | Mailpit |
|
|
215
|
+
| 8142, 8143 | xhgui |
|
|
216
|
+
|
|
217
|
+
Both layers now apply:
|
|
218
|
+
|
|
219
|
+
1. **`DOCKER-USER` rules**, installed by `setup`. That is the chain Docker
|
|
220
|
+
jumps to first in `FORWARD` and never flushes, so it is the only host-level
|
|
221
|
+
hook that can filter a published port. The rules drop traffic to the tool
|
|
222
|
+
ports unless it comes from Docker's own networks. They match on the
|
|
223
|
+
*original* destination port (`-m conntrack --ctorigdstport`), because by
|
|
224
|
+
`FORWARD` the destination has already been rewritten to the container.
|
|
225
|
+
|
|
226
|
+
They live in `/usr/local/sbin/trafic-docker-firewall`, reapplied at boot and
|
|
227
|
+
after any Docker restart by `trafic-docker-firewall.service` — Docker
|
|
228
|
+
recreates `DOCKER-USER` empty and nothing else would restore them.
|
|
229
|
+
**They will not appear in `ufw status`.** Inspect them with
|
|
230
|
+
`iptables -S DOCKER-USER`.
|
|
231
|
+
|
|
232
|
+
2. **Forward auth**, attached to every entry point ddev-router publishes, so a
|
|
233
|
+
request that does reach a tool port still needs credentials. An earlier
|
|
234
|
+
version attached it only to 80 and 443, which left xhgui answering from the
|
|
235
|
+
internet with no authentication at all.
|
|
236
|
+
|
|
237
|
+
Access from the host itself is unaffected: a connection to `127.0.0.1:8026`
|
|
238
|
+
goes through Docker's userland proxy rather than `FORWARD`, so an SSH tunnel
|
|
239
|
+
still works:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
ssh -L 8026:127.0.0.1:8026 ddev@server.example.com
|
|
243
|
+
# then open https://localhost:8026
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### A provider firewall is not a substitute
|
|
247
|
+
|
|
248
|
+
Recommended as an extra layer, but do not rely on it alone. Measured on an OVH
|
|
249
|
+
dedicated server with the Edge Firewall enabled and a correct rule denying
|
|
250
|
+
these ports: a connection from **another host inside OVH** still completed in
|
|
251
|
+
113ms, while a port with nothing listening was dropped and timed out at 12s.
|
|
252
|
+
The deny applies to traffic crossing the provider's edge; traffic that never
|
|
253
|
+
crosses it is not filtered.
|
|
254
|
+
|
|
255
|
+
Anyone able to rent a VM from the same provider is inside that blind spot,
|
|
256
|
+
which for a large host is a very low bar. That is why the `DOCKER-USER` rules
|
|
257
|
+
above exist: they apply to every packet regardless of origin.
|
|
258
|
+
|
|
259
|
+
On OVH the setting is the Network Firewall in the control panel. Two things to
|
|
260
|
+
know: it is *stateless*, so `permit tcp established` is required or return
|
|
261
|
+
traffic for outbound connections is dropped, and it is IPv4-only. `setup` does
|
|
262
|
+
not configure it — it can neither create nor verify it.
|
|
263
|
+
|
|
264
|
+
Binding ddev-router to loopback only (`router-bind-all-interfaces=false`)
|
|
265
|
+
closes everything at once, but then nothing serves the public and you need a
|
|
266
|
+
host proxy in front. That is a reasonable setup; it is just a different one
|
|
267
|
+
from what `setup` builds.
|
|
166
268
|
|
|
167
269
|
## License
|
|
168
270
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { E as
|
|
2
|
+
import { g as validateConfig, i as runPendingMigrations, m as loadConfig, n as setup, r as listMigrations, s as startIdleScheduler, t as audit } from "./setup-BFU-DlX3.js";
|
|
3
|
+
import { E as exec, M as step, N as success, O as info, P as warn, h as closeDb, k as isRoot, n as startServer } from "./server-CvSztHW2.js";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
import { execSync, spawnSync } from "node:child_process";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
@@ -108,7 +108,7 @@ function runUpgrade(dryRun = false, reExecArgs) {
|
|
|
108
108
|
}
|
|
109
109
|
const alreadyReExeced = process.env["TRAFIC_UPGRADE_REEXEC"] === "1";
|
|
110
110
|
step("Check for updates");
|
|
111
|
-
const current = "0.1.
|
|
111
|
+
const current = "0.1.38";
|
|
112
112
|
info(`Current version: ${current}`);
|
|
113
113
|
const latest = fetchLatestVersion();
|
|
114
114
|
if (!latest) warn("Could not reach npm registry — skipping version check");
|
|
@@ -191,7 +191,7 @@ function printHelp() {
|
|
|
191
191
|
console.log(HELP.trim());
|
|
192
192
|
}
|
|
193
193
|
function printVersion() {
|
|
194
|
-
console.log("0.1.
|
|
194
|
+
console.log("0.1.38");
|
|
195
195
|
}
|
|
196
196
|
async function runStart(values) {
|
|
197
197
|
const config = loadConfig(values.config);
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export type { AgentConfig, AuthConfig, AuthRule, AuthRequest, AuthResult, DdevPr
|
|
|
2
2
|
export { loadConfig, validateConfig, parseDuration } from "./utils/config.js";
|
|
3
3
|
export { checkAuth, matchIp, matchHostname, parseBasicAuth, parseBearerToken, } from "./utils/auth.js";
|
|
4
4
|
export { loadProjectList, buildHostnameIndex, getProjectFromHostname, watchProjectList, getProjectInfo, startProject, stopProject, listProjects, } from "./utils/ddev.js";
|
|
5
|
+
export { buildDockerFirewallScript, buildDockerFirewallUnit, configureDockerFirewall, DOCKER_FIREWALL_SCRIPT, DOCKER_FIREWALL_UNIT, } from "./setup/docker-firewall.js";
|
|
5
6
|
export { initDb, getDb, closeDb, updateProjectAccess, getProject, setProjectStatus, getIdleProjects, logAccess, getAccessLogs, cleanOldLogs, } from "./utils/db.js";
|
|
6
7
|
export { loadProjectConfig, shouldNeverStop, getIdleTimeoutMs, } from "./utils/project-config.js";
|
|
7
8
|
export type { ProjectConfig } from "./utils/project-config.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,aAAa,EACb,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG9E,OAAO,EACL,SAAS,EACT,OAAO,EACP,aAAa,EACb,cAAc,EACd,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,WAAW,EACX,YAAY,GACb,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,MAAM,EACN,KAAK,EACL,OAAO,EACP,mBAAmB,EACnB,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,aAAa,EACb,YAAY,GACb,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG5E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,WAAW,EACX,UAAU,EACV,QAAQ,EACR,WAAW,EACX,UAAU,EACV,WAAW,EACX,aAAa,EACb,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAG9E,OAAO,EACL,SAAS,EACT,OAAO,EACP,aAAa,EACb,cAAc,EACd,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,sBAAsB,EACtB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,WAAW,EACX,YAAY,GACb,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,EACvB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EACL,MAAM,EACN,KAAK,EACL,OAAO,EACP,mBAAmB,EACnB,UAAU,EACV,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,aAAa,EACb,YAAY,GACb,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,gBAAgB,GACjB,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG5E,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as printAuditResults, c as stopIdleProjects, d as validateConfig, l as loadConfig, n as setup, o as runAudit, s as startIdleScheduler, t as audit, u as
|
|
2
|
-
import {
|
|
3
|
-
export { audit, buildHostnameIndex, checkAuth, cleanOldLogs, closeDb, getAccessLogs, getDb, getIdleProjects, getIdleTimeoutMs, getProject, getProjectFromHostname, getProjectInfo, initDb, listProjects, loadConfig, loadProjectConfig, loadProjectList, logAccess, matchHostname, matchIp, parseBasicAuth, parseBearerToken, parseDuration, printAuditResults, runAudit, setProjectStatus, setup, shouldNeverStop, startIdleScheduler, startProject, startServer, stopIdleProjects, stopProject, updateProjectAccess, validateConfig, watchProjectList };
|
|
1
|
+
import { a as printAuditResults, c as stopIdleProjects, d as buildDockerFirewallScript, f as buildDockerFirewallUnit, g as validateConfig, h as parseDuration, l as DOCKER_FIREWALL_SCRIPT, m as loadConfig, n as setup, o as runAudit, p as configureDockerFirewall, s as startIdleScheduler, t as audit, u as DOCKER_FIREWALL_UNIT } from "./setup-BFU-DlX3.js";
|
|
2
|
+
import { B as startProject, C as updateProjectAccess, F as buildHostnameIndex, G as matchIp, H as watchProjectList, I as getProjectFromHostname, K as parseBasicAuth, L as getProjectInfo, R as listProjects, S as setProjectStatus, U as checkAuth, V as stopProject, W as matchHostname, _ as getDb, b as initDb, d as getIdleTimeoutMs, f as loadProjectConfig, g as getAccessLogs, h as closeDb, m as cleanOldLogs, n as startServer, p as shouldNeverStop, q as parseBearerToken, v as getIdleProjects, x as logAccess, y as getProject, z as loadProjectList } from "./server-CvSztHW2.js";
|
|
3
|
+
export { DOCKER_FIREWALL_SCRIPT, DOCKER_FIREWALL_UNIT, audit, buildDockerFirewallScript, buildDockerFirewallUnit, buildHostnameIndex, checkAuth, cleanOldLogs, closeDb, configureDockerFirewall, getAccessLogs, getDb, getIdleProjects, getIdleTimeoutMs, getProject, getProjectFromHostname, getProjectInfo, initDb, listProjects, loadConfig, loadProjectConfig, loadProjectList, logAccess, matchHostname, matchIp, parseBasicAuth, parseBearerToken, parseDuration, printAuditResults, runAudit, setProjectStatus, setup, shouldNeverStop, startIdleScheduler, startProject, startServer, stopIdleProjects, stopProject, updateProjectAccess, validateConfig, watchProjectList };
|
|
@@ -394,6 +394,131 @@ function listProjects() {
|
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
396
|
//#endregion
|
|
397
|
+
//#region src/setup/steps.ts
|
|
398
|
+
var stepNumber = 0;
|
|
399
|
+
var dryRun = false;
|
|
400
|
+
/**
|
|
401
|
+
* Set dry-run mode
|
|
402
|
+
*/
|
|
403
|
+
function setDryRun(value) {
|
|
404
|
+
dryRun = value;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Reset step counter
|
|
408
|
+
*/
|
|
409
|
+
function resetSteps() {
|
|
410
|
+
stepNumber = 0;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Log a step
|
|
414
|
+
*/
|
|
415
|
+
function step(message) {
|
|
416
|
+
stepNumber++;
|
|
417
|
+
console.log(`\n\x1b[36m[${stepNumber}]\x1b[0m ${message}`);
|
|
418
|
+
}
|
|
419
|
+
/**
|
|
420
|
+
* Log success
|
|
421
|
+
*/
|
|
422
|
+
function success(message) {
|
|
423
|
+
console.log(` \x1b[32m✓\x1b[0m ${message}`);
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Log warning
|
|
427
|
+
*/
|
|
428
|
+
function warn(message) {
|
|
429
|
+
console.log(` \x1b[33m⚠\x1b[0m ${message}`);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Log error
|
|
433
|
+
*/
|
|
434
|
+
function error(message) {
|
|
435
|
+
console.log(` \x1b[31m✗\x1b[0m ${message}`);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Log info
|
|
439
|
+
*/
|
|
440
|
+
function info(message) {
|
|
441
|
+
console.log(` ${message}`);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Execute a command (or show in dry-run mode)
|
|
445
|
+
*/
|
|
446
|
+
function exec(command, options) {
|
|
447
|
+
if (dryRun) {
|
|
448
|
+
if (!options?.silent) console.log(` \x1b[90m$ ${command}\x1b[0m`);
|
|
449
|
+
return "";
|
|
450
|
+
}
|
|
451
|
+
try {
|
|
452
|
+
return execSync(command, {
|
|
453
|
+
encoding: "utf-8",
|
|
454
|
+
stdio: options?.silent ? "pipe" : "inherit"
|
|
455
|
+
});
|
|
456
|
+
} catch (err) {
|
|
457
|
+
throw new Error(`Command failed: ${command}\n${err.stderr ?? ""}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Execute a command and return output (silent)
|
|
462
|
+
*/
|
|
463
|
+
function execSilent(command) {
|
|
464
|
+
if (dryRun) return "";
|
|
465
|
+
try {
|
|
466
|
+
return execSync(command, {
|
|
467
|
+
encoding: "utf-8",
|
|
468
|
+
stdio: "pipe"
|
|
469
|
+
}).trim();
|
|
470
|
+
} catch {
|
|
471
|
+
return "";
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Check if a command exists
|
|
476
|
+
*/
|
|
477
|
+
function commandExists(command) {
|
|
478
|
+
try {
|
|
479
|
+
execSync(`which ${command}`, { stdio: "pipe" });
|
|
480
|
+
return true;
|
|
481
|
+
} catch {
|
|
482
|
+
return false;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Check if running as root
|
|
487
|
+
*/
|
|
488
|
+
function isRoot() {
|
|
489
|
+
return process.getuid?.() === 0;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Write a file (or show in dry-run mode)
|
|
493
|
+
*/
|
|
494
|
+
function writeFile(path, content) {
|
|
495
|
+
if (dryRun) {
|
|
496
|
+
console.log(` \x1b[90mWrite ${path}:\x1b[0m`);
|
|
497
|
+
const lines = content.split("\n").slice(0, 5);
|
|
498
|
+
for (const line of lines) console.log(` \x1b[90m ${line}\x1b[0m`);
|
|
499
|
+
if (content.split("\n").length > 5) console.log(` \x1b[90m ...\x1b[0m`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
writeFileSync(path, content);
|
|
503
|
+
}
|
|
504
|
+
//#endregion
|
|
505
|
+
//#region src/setup/io.ts
|
|
506
|
+
/**
|
|
507
|
+
* The real implementation, used when no io is injected.
|
|
508
|
+
*
|
|
509
|
+
* `writeFile` comes from steps.js rather than `node:fs` so writes are
|
|
510
|
+
* skipped in dry-run mode. Reads stay direct: they change nothing.
|
|
511
|
+
*/
|
|
512
|
+
var nodeIo = {
|
|
513
|
+
exec,
|
|
514
|
+
execSilent,
|
|
515
|
+
commandExists,
|
|
516
|
+
writeFile,
|
|
517
|
+
fileExists: existsSync,
|
|
518
|
+
readFile: (path) => readFileSync(path, "utf-8"),
|
|
519
|
+
env: process.env
|
|
520
|
+
};
|
|
521
|
+
//#endregion
|
|
397
522
|
//#region src/utils/db.ts
|
|
398
523
|
var db = null;
|
|
399
524
|
/**
|
|
@@ -574,131 +699,6 @@ function getIdleTimeoutMs(config) {
|
|
|
574
699
|
return ms > 0 ? ms : void 0;
|
|
575
700
|
}
|
|
576
701
|
//#endregion
|
|
577
|
-
//#region src/setup/steps.ts
|
|
578
|
-
var stepNumber = 0;
|
|
579
|
-
var dryRun = false;
|
|
580
|
-
/**
|
|
581
|
-
* Set dry-run mode
|
|
582
|
-
*/
|
|
583
|
-
function setDryRun(value) {
|
|
584
|
-
dryRun = value;
|
|
585
|
-
}
|
|
586
|
-
/**
|
|
587
|
-
* Reset step counter
|
|
588
|
-
*/
|
|
589
|
-
function resetSteps() {
|
|
590
|
-
stepNumber = 0;
|
|
591
|
-
}
|
|
592
|
-
/**
|
|
593
|
-
* Log a step
|
|
594
|
-
*/
|
|
595
|
-
function step(message) {
|
|
596
|
-
stepNumber++;
|
|
597
|
-
console.log(`\n\x1b[36m[${stepNumber}]\x1b[0m ${message}`);
|
|
598
|
-
}
|
|
599
|
-
/**
|
|
600
|
-
* Log success
|
|
601
|
-
*/
|
|
602
|
-
function success(message) {
|
|
603
|
-
console.log(` \x1b[32m✓\x1b[0m ${message}`);
|
|
604
|
-
}
|
|
605
|
-
/**
|
|
606
|
-
* Log warning
|
|
607
|
-
*/
|
|
608
|
-
function warn(message) {
|
|
609
|
-
console.log(` \x1b[33m⚠\x1b[0m ${message}`);
|
|
610
|
-
}
|
|
611
|
-
/**
|
|
612
|
-
* Log error
|
|
613
|
-
*/
|
|
614
|
-
function error(message) {
|
|
615
|
-
console.log(` \x1b[31m✗\x1b[0m ${message}`);
|
|
616
|
-
}
|
|
617
|
-
/**
|
|
618
|
-
* Log info
|
|
619
|
-
*/
|
|
620
|
-
function info(message) {
|
|
621
|
-
console.log(` ${message}`);
|
|
622
|
-
}
|
|
623
|
-
/**
|
|
624
|
-
* Execute a command (or show in dry-run mode)
|
|
625
|
-
*/
|
|
626
|
-
function exec(command, options) {
|
|
627
|
-
if (dryRun) {
|
|
628
|
-
if (!options?.silent) console.log(` \x1b[90m$ ${command}\x1b[0m`);
|
|
629
|
-
return "";
|
|
630
|
-
}
|
|
631
|
-
try {
|
|
632
|
-
return execSync(command, {
|
|
633
|
-
encoding: "utf-8",
|
|
634
|
-
stdio: options?.silent ? "pipe" : "inherit"
|
|
635
|
-
});
|
|
636
|
-
} catch (err) {
|
|
637
|
-
throw new Error(`Command failed: ${command}\n${err.stderr ?? ""}`);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Execute a command and return output (silent)
|
|
642
|
-
*/
|
|
643
|
-
function execSilent(command) {
|
|
644
|
-
if (dryRun) return "";
|
|
645
|
-
try {
|
|
646
|
-
return execSync(command, {
|
|
647
|
-
encoding: "utf-8",
|
|
648
|
-
stdio: "pipe"
|
|
649
|
-
}).trim();
|
|
650
|
-
} catch {
|
|
651
|
-
return "";
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
/**
|
|
655
|
-
* Check if a command exists
|
|
656
|
-
*/
|
|
657
|
-
function commandExists(command) {
|
|
658
|
-
try {
|
|
659
|
-
execSync(`which ${command}`, { stdio: "pipe" });
|
|
660
|
-
return true;
|
|
661
|
-
} catch {
|
|
662
|
-
return false;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
/**
|
|
666
|
-
* Check if running as root
|
|
667
|
-
*/
|
|
668
|
-
function isRoot() {
|
|
669
|
-
return process.getuid?.() === 0;
|
|
670
|
-
}
|
|
671
|
-
/**
|
|
672
|
-
* Write a file (or show in dry-run mode)
|
|
673
|
-
*/
|
|
674
|
-
function writeFile(path, content) {
|
|
675
|
-
if (dryRun) {
|
|
676
|
-
console.log(` \x1b[90mWrite ${path}:\x1b[0m`);
|
|
677
|
-
const lines = content.split("\n").slice(0, 5);
|
|
678
|
-
for (const line of lines) console.log(` \x1b[90m ${line}\x1b[0m`);
|
|
679
|
-
if (content.split("\n").length > 5) console.log(` \x1b[90m ...\x1b[0m`);
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
writeFileSync(path, content);
|
|
683
|
-
}
|
|
684
|
-
//#endregion
|
|
685
|
-
//#region src/setup/io.ts
|
|
686
|
-
/**
|
|
687
|
-
* The real implementation, used when no io is injected.
|
|
688
|
-
*
|
|
689
|
-
* `writeFile` comes from steps.js rather than `node:fs` so writes are
|
|
690
|
-
* skipped in dry-run mode. Reads stay direct: they change nothing.
|
|
691
|
-
*/
|
|
692
|
-
var nodeIo = {
|
|
693
|
-
exec,
|
|
694
|
-
execSilent,
|
|
695
|
-
commandExists,
|
|
696
|
-
writeFile,
|
|
697
|
-
fileExists: existsSync,
|
|
698
|
-
readFile: (path) => readFileSync(path, "utf-8"),
|
|
699
|
-
env: process.env
|
|
700
|
-
};
|
|
701
|
-
//#endregion
|
|
702
702
|
//#region src/setup/ddev.ts
|
|
703
703
|
/**
|
|
704
704
|
* Install system dependencies required by Trafic
|
|
@@ -1110,31 +1110,6 @@ async function handleErrors(req, res) {
|
|
|
1110
1110
|
});
|
|
1111
1111
|
}
|
|
1112
1112
|
/**
|
|
1113
|
-
* Decide whether Caddy may obtain a certificate for a hostname.
|
|
1114
|
-
*
|
|
1115
|
-
* Caddy's on-demand TLS asks before issuing for a name it has not seen.
|
|
1116
|
-
* Without a check, anyone pointing a DNS record at this server would make it
|
|
1117
|
-
* request a certificate, and Let's Encrypt quota is per registered domain —
|
|
1118
|
-
* a handful of strangers could exhaust it for every preview.
|
|
1119
|
-
*/
|
|
1120
|
-
function allowTlsFor(domain, index) {
|
|
1121
|
-
return index.has(domain.toLowerCase());
|
|
1122
|
-
}
|
|
1123
|
-
/**
|
|
1124
|
-
* Handle Caddy's on-demand TLS ask. 2xx allows issuance, anything else denies.
|
|
1125
|
-
*/
|
|
1126
|
-
function handleTlsAsk(req, res) {
|
|
1127
|
-
const domain = new URL(req.url ?? "/", `http://${req.headers.host}`).searchParams.get("domain") ?? "";
|
|
1128
|
-
if (allowTlsFor(domain, hostnameIndex)) {
|
|
1129
|
-
res.writeHead(200);
|
|
1130
|
-
res.end("ok");
|
|
1131
|
-
return;
|
|
1132
|
-
}
|
|
1133
|
-
console.log(`TLS ask denied for unknown hostname: ${domain || "(none)"}`);
|
|
1134
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1135
|
-
res.end("Unknown project");
|
|
1136
|
-
}
|
|
1137
|
-
/**
|
|
1138
1113
|
* Handle status polling requests
|
|
1139
1114
|
*/
|
|
1140
1115
|
async function handleStatus(req, res) {
|
|
@@ -1178,7 +1153,6 @@ async function handleRequest(req, res) {
|
|
|
1178
1153
|
try {
|
|
1179
1154
|
if (path === "/__auth__" || path.startsWith("/__auth__/")) handleAuth(req, res);
|
|
1180
1155
|
else if (path === "/__status__" || path.startsWith("/__status__/")) await handleStatus(req, res);
|
|
1181
|
-
else if (path === "/__tls__") handleTlsAsk(req, res);
|
|
1182
1156
|
else if (path === "/__health__") {
|
|
1183
1157
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
1184
1158
|
res.end(JSON.stringify({
|
|
@@ -1235,6 +1209,6 @@ function startServer(agentConfig) {
|
|
|
1235
1209
|
});
|
|
1236
1210
|
}
|
|
1237
1211
|
//#endregion
|
|
1238
|
-
export {
|
|
1212
|
+
export { resetSteps as A, startProject as B, updateProjectAccess as C, execSilent as D, exec as E, buildHostnameIndex as F, matchIp as G, watchProjectList as H, getProjectFromHostname as I, parseBasicAuth as K, getProjectInfo as L, step as M, success as N, info as O, warn as P, listProjects as R, setProjectStatus as S, error as T, checkAuth as U, stopProject as V, matchHostname as W, getDb as _, configureTraefik as a, initDb as b, installSystemDeps as c, getIdleTimeoutMs as d, loadProjectConfig as f, getAccessLogs as g, closeDb as h, configureDdev as i, setDryRun as j, isRoot as k, purgeTraefikAcmeStorage as l, cleanOldLogs as m, startServer as n, getDockerGatewayIp as o, shouldNeverStop as p, parseBearerToken as q, addDdevAptRepo as r, installDdev as s, routePath as t, readToolPorts as u, getIdleProjects as v, nodeIo as w, logAccess as x, getProject as y, loadProjectList as z };
|
|
1239
1213
|
|
|
1240
|
-
//# sourceMappingURL=server-
|
|
1214
|
+
//# sourceMappingURL=server-CvSztHW2.js.map
|