@studiometa/trafic-agent 0.1.7 → 0.1.9
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/dist/cli.js +92 -89
- package/dist/cli.js.map +1 -1
- package/dist/index.js +3 -41
- package/dist/server-D0N_6y4c.js +687 -0
- package/dist/server-D0N_6y4c.js.map +1 -0
- package/dist/server.js +2 -9
- package/dist/setup/ddev.d.ts.map +1 -1
- package/dist/setup-CVf8YfPW.js +941 -0
- package/dist/setup-CVf8YfPW.js.map +1 -0
- package/package.json +6 -6
- package/dist/index-Dip9RQ_T.js +0 -904
- package/dist/index-Dip9RQ_T.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/server-CHT47EXr.js +0 -618
- package/dist/server-CHT47EXr.js.map +0 -1
- package/dist/server.js.map +0 -1
package/dist/server-CHT47EXr.js
DELETED
|
@@ -1,618 +0,0 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
|
-
import { existsSync, watch, readFileSync } from "node:fs";
|
|
3
|
-
import { join, dirname, resolve } from "node:path";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { execSync } from "node:child_process";
|
|
6
|
-
import Database from "better-sqlite3";
|
|
7
|
-
function matchIp(ip, pattern) {
|
|
8
|
-
if (ip === pattern) return true;
|
|
9
|
-
if (pattern.includes("/")) {
|
|
10
|
-
return matchCidr(ip, pattern);
|
|
11
|
-
}
|
|
12
|
-
if (pattern.includes("*")) {
|
|
13
|
-
const regex = new RegExp(
|
|
14
|
-
"^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$"
|
|
15
|
-
);
|
|
16
|
-
return regex.test(ip);
|
|
17
|
-
}
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
function matchCidr(ip, cidr) {
|
|
21
|
-
const [range, bits] = cidr.split("/");
|
|
22
|
-
const mask = parseInt(bits, 10);
|
|
23
|
-
const ipNum = ipToNumber(ip);
|
|
24
|
-
const rangeNum = ipToNumber(range);
|
|
25
|
-
if (ipNum === null || rangeNum === null) return false;
|
|
26
|
-
const maskNum = ~((1 << 32 - mask) - 1);
|
|
27
|
-
return (ipNum & maskNum) === (rangeNum & maskNum);
|
|
28
|
-
}
|
|
29
|
-
function ipToNumber(ip) {
|
|
30
|
-
const parts = ip.split(".");
|
|
31
|
-
if (parts.length !== 4) return null;
|
|
32
|
-
let num = 0;
|
|
33
|
-
for (const part of parts) {
|
|
34
|
-
const n = parseInt(part, 10);
|
|
35
|
-
if (isNaN(n) || n < 0 || n > 255) return null;
|
|
36
|
-
num = num << 8 | n;
|
|
37
|
-
}
|
|
38
|
-
return num >>> 0;
|
|
39
|
-
}
|
|
40
|
-
function matchHostname(hostname, pattern) {
|
|
41
|
-
if (hostname === pattern) return true;
|
|
42
|
-
const regex = new RegExp(
|
|
43
|
-
"^" + pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".") + "$"
|
|
44
|
-
);
|
|
45
|
-
return regex.test(hostname);
|
|
46
|
-
}
|
|
47
|
-
function parseBasicAuth(header) {
|
|
48
|
-
if (!header.startsWith("Basic ")) return null;
|
|
49
|
-
try {
|
|
50
|
-
const encoded = header.slice(6);
|
|
51
|
-
const decoded = Buffer.from(encoded, "base64").toString("utf-8");
|
|
52
|
-
const colonIndex = decoded.indexOf(":");
|
|
53
|
-
if (colonIndex === -1) return null;
|
|
54
|
-
return {
|
|
55
|
-
username: decoded.slice(0, colonIndex),
|
|
56
|
-
password: decoded.slice(colonIndex + 1)
|
|
57
|
-
};
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function parseBearerToken(header) {
|
|
63
|
-
if (!header.startsWith("Bearer ")) return null;
|
|
64
|
-
return header.slice(7);
|
|
65
|
-
}
|
|
66
|
-
function checkAuth(request, config2) {
|
|
67
|
-
const { hostname, ip, authorization } = request;
|
|
68
|
-
const realIp = request.forwardedFor?.split(",")[0].trim() ?? ip;
|
|
69
|
-
for (const pattern of config2.allowedIps) {
|
|
70
|
-
if (matchIp(realIp, pattern)) {
|
|
71
|
-
return { allowed: true, reason: "ip" };
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
for (const rule of config2.rules) {
|
|
75
|
-
if (matchHostname(hostname, rule.match)) {
|
|
76
|
-
return checkRule(rule, realIp, authorization, config2);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
return applyDefaultPolicy(config2, realIp, authorization);
|
|
80
|
-
}
|
|
81
|
-
function checkRule(rule, ip, authorization, config2) {
|
|
82
|
-
if (rule.allowedIps) {
|
|
83
|
-
for (const pattern of rule.allowedIps) {
|
|
84
|
-
if (matchIp(ip, pattern)) {
|
|
85
|
-
return { allowed: true, reason: "rule" };
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
switch (rule.policy) {
|
|
90
|
-
case "allow":
|
|
91
|
-
return { allowed: true, reason: "rule" };
|
|
92
|
-
case "deny":
|
|
93
|
-
return { allowed: false, reason: "rule" };
|
|
94
|
-
case "token": {
|
|
95
|
-
if (!authorization) return { allowed: false, reason: "rule" };
|
|
96
|
-
const token = parseBearerToken(authorization);
|
|
97
|
-
const tokens = rule.tokens ?? config2.tokens;
|
|
98
|
-
if (token && tokens.includes(token)) {
|
|
99
|
-
return { allowed: true, reason: "token" };
|
|
100
|
-
}
|
|
101
|
-
return { allowed: false, reason: "rule" };
|
|
102
|
-
}
|
|
103
|
-
case "basic": {
|
|
104
|
-
if (!authorization) return { allowed: false, reason: "rule" };
|
|
105
|
-
const creds = parseBasicAuth(authorization);
|
|
106
|
-
if (creds) {
|
|
107
|
-
const credString = `${creds.username}:${creds.password}`;
|
|
108
|
-
if (config2.basicAuth.includes(credString)) {
|
|
109
|
-
return { allowed: true, reason: "basic" };
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
return { allowed: false, reason: "rule" };
|
|
113
|
-
}
|
|
114
|
-
default:
|
|
115
|
-
return { allowed: false, reason: "rule" };
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
function applyDefaultPolicy(config2, ip, authorization) {
|
|
119
|
-
switch (config2.defaultPolicy) {
|
|
120
|
-
case "allow":
|
|
121
|
-
return { allowed: true, reason: "default" };
|
|
122
|
-
case "deny":
|
|
123
|
-
return { allowed: false, reason: "default" };
|
|
124
|
-
case "basic": {
|
|
125
|
-
if (!authorization) return { allowed: false, reason: "default" };
|
|
126
|
-
const creds = parseBasicAuth(authorization);
|
|
127
|
-
if (creds) {
|
|
128
|
-
const credString = `${creds.username}:${creds.password}`;
|
|
129
|
-
if (config2.basicAuth.includes(credString)) {
|
|
130
|
-
return { allowed: true, reason: "basic" };
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
const token = parseBearerToken(authorization);
|
|
134
|
-
if (token && config2.tokens.includes(token)) {
|
|
135
|
-
return { allowed: true, reason: "token" };
|
|
136
|
-
}
|
|
137
|
-
return { allowed: false, reason: "default" };
|
|
138
|
-
}
|
|
139
|
-
case "token": {
|
|
140
|
-
if (!authorization) return { allowed: false, reason: "default" };
|
|
141
|
-
const token = parseBearerToken(authorization);
|
|
142
|
-
if (token && config2.tokens.includes(token)) {
|
|
143
|
-
return { allowed: true, reason: "token" };
|
|
144
|
-
}
|
|
145
|
-
return { allowed: false, reason: "default" };
|
|
146
|
-
}
|
|
147
|
-
default:
|
|
148
|
-
return { allowed: false, reason: "default" };
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function parseSimpleYaml$1(content) {
|
|
152
|
-
const result = {};
|
|
153
|
-
const lines = content.split("\n");
|
|
154
|
-
for (const line of lines) {
|
|
155
|
-
const trimmed = line.trim();
|
|
156
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
157
|
-
const colonIndex = trimmed.indexOf(":");
|
|
158
|
-
if (colonIndex === -1) continue;
|
|
159
|
-
const key = trimmed.slice(0, colonIndex).trim();
|
|
160
|
-
let value = trimmed.slice(colonIndex + 1).trim();
|
|
161
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
162
|
-
value = value.slice(1, -1);
|
|
163
|
-
}
|
|
164
|
-
result[key] = value;
|
|
165
|
-
}
|
|
166
|
-
return result;
|
|
167
|
-
}
|
|
168
|
-
function loadProjectList(projectListPath) {
|
|
169
|
-
if (!existsSync(projectListPath)) {
|
|
170
|
-
return /* @__PURE__ */ new Map();
|
|
171
|
-
}
|
|
172
|
-
const content = readFileSync(projectListPath, "utf-8");
|
|
173
|
-
const parsed = parseSimpleYaml$1(content);
|
|
174
|
-
return new Map(Object.entries(parsed));
|
|
175
|
-
}
|
|
176
|
-
function buildHostnameIndex(projects, tld) {
|
|
177
|
-
const index = /* @__PURE__ */ new Map();
|
|
178
|
-
for (const [name] of projects) {
|
|
179
|
-
index.set(`${name}.${tld}`, name);
|
|
180
|
-
}
|
|
181
|
-
return index;
|
|
182
|
-
}
|
|
183
|
-
function getProjectFromHostname(hostname, index) {
|
|
184
|
-
return index.get(hostname);
|
|
185
|
-
}
|
|
186
|
-
function watchProjectList(projectListPath, onChange) {
|
|
187
|
-
if (!existsSync(projectListPath)) {
|
|
188
|
-
console.warn(`Project list not found: ${projectListPath}`);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
watch(projectListPath, (eventType) => {
|
|
192
|
-
if (eventType === "change") {
|
|
193
|
-
onChange();
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
function getProjectInfo(name) {
|
|
198
|
-
try {
|
|
199
|
-
const output = execSync(`ddev describe ${name} -j`, {
|
|
200
|
-
encoding: "utf-8",
|
|
201
|
-
timeout: 1e4
|
|
202
|
-
});
|
|
203
|
-
const data = JSON.parse(output);
|
|
204
|
-
if (!data.raw) return void 0;
|
|
205
|
-
const raw = data.raw;
|
|
206
|
-
return {
|
|
207
|
-
name: raw.name,
|
|
208
|
-
status: raw.status,
|
|
209
|
-
appRoot: raw.approot,
|
|
210
|
-
httpURLs: raw.httpURLs ?? [],
|
|
211
|
-
httpsURLs: raw.httpsURLs ?? [],
|
|
212
|
-
type: raw.type,
|
|
213
|
-
phpVersion: raw.php_version,
|
|
214
|
-
dbType: raw.dbinfo?.dbType
|
|
215
|
-
};
|
|
216
|
-
} catch {
|
|
217
|
-
return void 0;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
function startProject(name) {
|
|
221
|
-
try {
|
|
222
|
-
execSync(`ddev start ${name}`, {
|
|
223
|
-
encoding: "utf-8",
|
|
224
|
-
timeout: 12e4,
|
|
225
|
-
// 2 minutes
|
|
226
|
-
stdio: "pipe"
|
|
227
|
-
});
|
|
228
|
-
return true;
|
|
229
|
-
} catch (error) {
|
|
230
|
-
console.error(`Failed to start project ${name}:`, error);
|
|
231
|
-
return false;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
function stopProject(name) {
|
|
235
|
-
try {
|
|
236
|
-
execSync(`ddev stop ${name}`, {
|
|
237
|
-
encoding: "utf-8",
|
|
238
|
-
timeout: 6e4,
|
|
239
|
-
// 1 minute
|
|
240
|
-
stdio: "pipe"
|
|
241
|
-
});
|
|
242
|
-
return true;
|
|
243
|
-
} catch (error) {
|
|
244
|
-
console.error(`Failed to stop project ${name}:`, error);
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
function listProjects() {
|
|
249
|
-
try {
|
|
250
|
-
const output = execSync("ddev list -j", {
|
|
251
|
-
encoding: "utf-8",
|
|
252
|
-
timeout: 3e4
|
|
253
|
-
});
|
|
254
|
-
const data = JSON.parse(output);
|
|
255
|
-
if (!Array.isArray(data)) return [];
|
|
256
|
-
return data.map((p) => ({
|
|
257
|
-
name: p.name,
|
|
258
|
-
status: p.status,
|
|
259
|
-
appRoot: p.approot,
|
|
260
|
-
httpURLs: p.httpURLs ?? [],
|
|
261
|
-
httpsURLs: p.httpsURLs ?? [],
|
|
262
|
-
type: p.type
|
|
263
|
-
}));
|
|
264
|
-
} catch {
|
|
265
|
-
return [];
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
let db = null;
|
|
269
|
-
function initDb(dbPath) {
|
|
270
|
-
db = new Database(dbPath);
|
|
271
|
-
db.pragma("journal_mode = WAL");
|
|
272
|
-
db.exec(`
|
|
273
|
-
CREATE TABLE IF NOT EXISTS projects (
|
|
274
|
-
name TEXT PRIMARY KEY,
|
|
275
|
-
last_access INTEGER NOT NULL,
|
|
276
|
-
status TEXT NOT NULL DEFAULT 'stopped'
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
CREATE TABLE IF NOT EXISTS access_logs (
|
|
280
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
281
|
-
project TEXT NOT NULL,
|
|
282
|
-
timestamp INTEGER NOT NULL,
|
|
283
|
-
ip TEXT NOT NULL,
|
|
284
|
-
user_agent TEXT NOT NULL,
|
|
285
|
-
path TEXT NOT NULL
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
CREATE INDEX IF NOT EXISTS idx_access_logs_project ON access_logs(project);
|
|
289
|
-
CREATE INDEX IF NOT EXISTS idx_access_logs_timestamp ON access_logs(timestamp);
|
|
290
|
-
`);
|
|
291
|
-
return db;
|
|
292
|
-
}
|
|
293
|
-
function getDb() {
|
|
294
|
-
if (!db) {
|
|
295
|
-
throw new Error("Database not initialized. Call initDb first.");
|
|
296
|
-
}
|
|
297
|
-
return db;
|
|
298
|
-
}
|
|
299
|
-
function closeDb() {
|
|
300
|
-
if (db) {
|
|
301
|
-
db.close();
|
|
302
|
-
db = null;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
function updateProjectAccess(name) {
|
|
306
|
-
const database = getDb();
|
|
307
|
-
const stmt = database.prepare(`
|
|
308
|
-
INSERT INTO projects (name, last_access, status)
|
|
309
|
-
VALUES (?, ?, 'running')
|
|
310
|
-
ON CONFLICT(name) DO UPDATE SET last_access = excluded.last_access
|
|
311
|
-
`);
|
|
312
|
-
stmt.run(name, Date.now());
|
|
313
|
-
}
|
|
314
|
-
function getProject(name) {
|
|
315
|
-
const database = getDb();
|
|
316
|
-
const stmt = database.prepare(
|
|
317
|
-
"SELECT name, last_access as lastAccess, status FROM projects WHERE name = ?"
|
|
318
|
-
);
|
|
319
|
-
return stmt.get(name);
|
|
320
|
-
}
|
|
321
|
-
function setProjectStatus(name, status) {
|
|
322
|
-
const database = getDb();
|
|
323
|
-
const stmt = database.prepare(`
|
|
324
|
-
INSERT INTO projects (name, last_access, status)
|
|
325
|
-
VALUES (?, ?, ?)
|
|
326
|
-
ON CONFLICT(name) DO UPDATE SET status = excluded.status
|
|
327
|
-
`);
|
|
328
|
-
stmt.run(name, Date.now(), status);
|
|
329
|
-
}
|
|
330
|
-
function getIdleProjects(thresholdMs) {
|
|
331
|
-
const database = getDb();
|
|
332
|
-
const cutoff = Date.now() - thresholdMs;
|
|
333
|
-
const stmt = database.prepare(`
|
|
334
|
-
SELECT name, last_access as lastAccess, status
|
|
335
|
-
FROM projects
|
|
336
|
-
WHERE last_access < ? AND status = 'running'
|
|
337
|
-
`);
|
|
338
|
-
return stmt.all(cutoff);
|
|
339
|
-
}
|
|
340
|
-
function logAccess(log) {
|
|
341
|
-
const database = getDb();
|
|
342
|
-
const stmt = database.prepare(`
|
|
343
|
-
INSERT INTO access_logs (project, timestamp, ip, user_agent, path)
|
|
344
|
-
VALUES (?, ?, ?, ?, ?)
|
|
345
|
-
`);
|
|
346
|
-
stmt.run(log.project, log.timestamp, log.ip, log.userAgent, log.path);
|
|
347
|
-
}
|
|
348
|
-
function getAccessLogs(project, limit = 100) {
|
|
349
|
-
const database = getDb();
|
|
350
|
-
const stmt = database.prepare(`
|
|
351
|
-
SELECT id, project, timestamp, ip, user_agent as userAgent, path
|
|
352
|
-
FROM access_logs
|
|
353
|
-
WHERE project = ?
|
|
354
|
-
ORDER BY timestamp DESC
|
|
355
|
-
LIMIT ?
|
|
356
|
-
`);
|
|
357
|
-
return stmt.all(project, limit);
|
|
358
|
-
}
|
|
359
|
-
function cleanOldLogs(days) {
|
|
360
|
-
const database = getDb();
|
|
361
|
-
const cutoff = Date.now() - days * 24 * 60 * 60 * 1e3;
|
|
362
|
-
const stmt = database.prepare("DELETE FROM access_logs WHERE timestamp < ?");
|
|
363
|
-
const result = stmt.run(cutoff);
|
|
364
|
-
return result.changes;
|
|
365
|
-
}
|
|
366
|
-
function parseSimpleYaml(content) {
|
|
367
|
-
const result = {};
|
|
368
|
-
const lines = content.split("\n");
|
|
369
|
-
for (const line of lines) {
|
|
370
|
-
const trimmed = line.trim();
|
|
371
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
372
|
-
const colonIndex = trimmed.indexOf(":");
|
|
373
|
-
if (colonIndex === -1) continue;
|
|
374
|
-
const key = trimmed.slice(0, colonIndex).trim();
|
|
375
|
-
let value = trimmed.slice(colonIndex + 1).trim();
|
|
376
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
377
|
-
value = value.slice(1, -1);
|
|
378
|
-
}
|
|
379
|
-
result[key] = value;
|
|
380
|
-
}
|
|
381
|
-
return result;
|
|
382
|
-
}
|
|
383
|
-
function loadProjectConfig(projectDir) {
|
|
384
|
-
const configPath = join(projectDir, ".ddev", "config.trafic.yaml");
|
|
385
|
-
if (!existsSync(configPath)) {
|
|
386
|
-
return {};
|
|
387
|
-
}
|
|
388
|
-
try {
|
|
389
|
-
const content = readFileSync(configPath, "utf-8");
|
|
390
|
-
const parsed = parseSimpleYaml(content);
|
|
391
|
-
const config2 = {};
|
|
392
|
-
if (parsed.auth_policy) {
|
|
393
|
-
const policy = parsed.auth_policy.toLowerCase();
|
|
394
|
-
if (["allow", "deny", "basic", "token"].includes(policy)) {
|
|
395
|
-
config2.auth_policy = policy;
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
if (parsed.idle_timeout) {
|
|
399
|
-
config2.idle_timeout = parsed.idle_timeout;
|
|
400
|
-
}
|
|
401
|
-
return config2;
|
|
402
|
-
} catch (error) {
|
|
403
|
-
console.warn(`Failed to load project config from ${configPath}:`, error);
|
|
404
|
-
return {};
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
function shouldNeverStop(config2) {
|
|
408
|
-
return config2.idle_timeout?.toLowerCase() === "never";
|
|
409
|
-
}
|
|
410
|
-
function getIdleTimeoutMs(config2) {
|
|
411
|
-
if (!config2.idle_timeout) return void 0;
|
|
412
|
-
if (shouldNeverStop(config2)) return void 0;
|
|
413
|
-
const regex = /(?:(\d+)h)?(?:(\d+)m)?(?:(\d+)s)?/;
|
|
414
|
-
const match = config2.idle_timeout.match(regex);
|
|
415
|
-
if (!match) return void 0;
|
|
416
|
-
const hours = parseInt(match[1] ?? "0", 10);
|
|
417
|
-
const minutes = parseInt(match[2] ?? "0", 10);
|
|
418
|
-
const seconds = parseInt(match[3] ?? "0", 10);
|
|
419
|
-
const ms = (hours * 3600 + minutes * 60 + seconds) * 1e3;
|
|
420
|
-
return ms > 0 ? ms : void 0;
|
|
421
|
-
}
|
|
422
|
-
const __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
423
|
-
let config;
|
|
424
|
-
let projectList;
|
|
425
|
-
let hostnameIndex;
|
|
426
|
-
const projectConfigs = /* @__PURE__ */ new Map();
|
|
427
|
-
function loadTemplate(name) {
|
|
428
|
-
const templatePath = resolve(__dirname$1, "..", "templates", `${name}.html`);
|
|
429
|
-
try {
|
|
430
|
-
return readFileSync(templatePath, "utf-8");
|
|
431
|
-
} catch {
|
|
432
|
-
return `<html><body><h1>${name}</h1></body></html>`;
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
function getEffectiveAuthConfig(projectName) {
|
|
436
|
-
if (!projectName) return config.auth;
|
|
437
|
-
const projectConfig = projectConfigs.get(projectName);
|
|
438
|
-
if (!projectConfig?.auth_policy) return config.auth;
|
|
439
|
-
return {
|
|
440
|
-
...config.auth,
|
|
441
|
-
defaultPolicy: projectConfig.auth_policy
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
function handleAuth(req, res) {
|
|
445
|
-
const hostname = req.headers["x-forwarded-host"] ?? "";
|
|
446
|
-
const ip = req.headers["x-forwarded-for"] ?? req.socket.remoteAddress ?? "";
|
|
447
|
-
const authorization = req.headers["authorization"];
|
|
448
|
-
const path = req.headers["x-forwarded-uri"] ?? "/";
|
|
449
|
-
const projectName = hostnameIndex.get(hostname);
|
|
450
|
-
const authConfig = getEffectiveAuthConfig(projectName);
|
|
451
|
-
const result = checkAuth(
|
|
452
|
-
{
|
|
453
|
-
hostname,
|
|
454
|
-
ip: ip.split(",")[0].trim(),
|
|
455
|
-
authorization,
|
|
456
|
-
forwardedFor: ip
|
|
457
|
-
},
|
|
458
|
-
authConfig
|
|
459
|
-
);
|
|
460
|
-
if (result.allowed) {
|
|
461
|
-
if (projectName) {
|
|
462
|
-
updateProjectAccess(projectName);
|
|
463
|
-
logAccess({
|
|
464
|
-
project: projectName,
|
|
465
|
-
timestamp: Date.now(),
|
|
466
|
-
ip: ip.split(",")[0].trim(),
|
|
467
|
-
userAgent: req.headers["user-agent"] ?? "",
|
|
468
|
-
path
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
res.writeHead(200);
|
|
472
|
-
res.end();
|
|
473
|
-
} else {
|
|
474
|
-
res.writeHead(401, {
|
|
475
|
-
"WWW-Authenticate": 'Basic realm="Trafic"'
|
|
476
|
-
});
|
|
477
|
-
res.end("Unauthorized");
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
async function handleErrors(req, res) {
|
|
481
|
-
const hostname = req.headers["x-forwarded-host"] ?? req.headers.host ?? "";
|
|
482
|
-
const projectName = hostnameIndex.get(hostname);
|
|
483
|
-
if (!projectName) {
|
|
484
|
-
const template2 = loadTemplate("error");
|
|
485
|
-
res.writeHead(404, { "Content-Type": "text/html" });
|
|
486
|
-
res.end(template2.replace("{{message}}", "Project not found"));
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
const record = getProject(projectName);
|
|
490
|
-
if (record?.status === "starting") {
|
|
491
|
-
const template2 = loadTemplate("wait");
|
|
492
|
-
res.writeHead(503, {
|
|
493
|
-
"Content-Type": "text/html",
|
|
494
|
-
"Retry-After": "5"
|
|
495
|
-
});
|
|
496
|
-
res.end(
|
|
497
|
-
template2.replace(/\{\{project\}\}/g, projectName).replace(/\{\{hostname\}\}/g, hostname)
|
|
498
|
-
);
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
setProjectStatus(projectName, "starting");
|
|
502
|
-
const template = loadTemplate("wait");
|
|
503
|
-
res.writeHead(503, {
|
|
504
|
-
"Content-Type": "text/html",
|
|
505
|
-
"Retry-After": "5"
|
|
506
|
-
});
|
|
507
|
-
res.end(
|
|
508
|
-
template.replace(/\{\{project\}\}/g, projectName).replace(/\{\{hostname\}\}/g, hostname)
|
|
509
|
-
);
|
|
510
|
-
setTimeout(() => {
|
|
511
|
-
const success = startProject(projectName);
|
|
512
|
-
setProjectStatus(projectName, success ? "running" : "stopped");
|
|
513
|
-
}, 100);
|
|
514
|
-
}
|
|
515
|
-
function handleStatus(req, res) {
|
|
516
|
-
const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
|
|
517
|
-
const projectName = url.searchParams.get("project");
|
|
518
|
-
if (!projectName) {
|
|
519
|
-
res.writeHead(400, { "Content-Type": "application/json" });
|
|
520
|
-
res.end(JSON.stringify({ error: "Missing project parameter" }));
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
const info = getProjectInfo(projectName);
|
|
524
|
-
const record = getProject(projectName);
|
|
525
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
526
|
-
res.end(
|
|
527
|
-
JSON.stringify({
|
|
528
|
-
name: projectName,
|
|
529
|
-
status: info?.status ?? record?.status ?? "unknown",
|
|
530
|
-
ready: info?.status === "running"
|
|
531
|
-
})
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
async function handleRequest(req, res) {
|
|
535
|
-
const url = req.url ?? "/";
|
|
536
|
-
try {
|
|
537
|
-
if (url === "/__auth__" || url.startsWith("/__auth__/")) {
|
|
538
|
-
handleAuth(req, res);
|
|
539
|
-
} else if (url === "/__status__" || url.startsWith("/__status__/")) {
|
|
540
|
-
handleStatus(req, res);
|
|
541
|
-
} else if (url === "/__health__") {
|
|
542
|
-
res.writeHead(200, { "Content-Type": "application/json" });
|
|
543
|
-
res.end(JSON.stringify({ status: "ok", version: "__VERSION__" }));
|
|
544
|
-
} else {
|
|
545
|
-
await handleErrors(req, res);
|
|
546
|
-
}
|
|
547
|
-
} catch (error) {
|
|
548
|
-
console.error("Request error:", error);
|
|
549
|
-
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
550
|
-
res.end("Internal Server Error");
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
function reloadProjects() {
|
|
554
|
-
projectList = loadProjectList(config.projectListPath);
|
|
555
|
-
hostnameIndex = buildHostnameIndex(projectList, config.tld);
|
|
556
|
-
projectConfigs.clear();
|
|
557
|
-
for (const [name, projectDir] of projectList) {
|
|
558
|
-
const projectConfig = loadProjectConfig(projectDir);
|
|
559
|
-
if (projectConfig.auth_policy || projectConfig.idle_timeout) {
|
|
560
|
-
projectConfigs.set(name, projectConfig);
|
|
561
|
-
console.log(` ${name}: auth=${projectConfig.auth_policy ?? "default"}, idle=${projectConfig.idle_timeout ?? "default"}`);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
console.log(`Loaded ${projectList.size} projects (${projectConfigs.size} with custom config)`);
|
|
565
|
-
}
|
|
566
|
-
function startServer(agentConfig) {
|
|
567
|
-
config = agentConfig;
|
|
568
|
-
initDb(config.dbPath);
|
|
569
|
-
reloadProjects();
|
|
570
|
-
watchProjectList(config.projectListPath, () => {
|
|
571
|
-
console.log("Project list changed, reloading...");
|
|
572
|
-
reloadProjects();
|
|
573
|
-
});
|
|
574
|
-
const server = createServer((req, res) => {
|
|
575
|
-
handleRequest(req, res).catch((error) => {
|
|
576
|
-
console.error("Unhandled error:", error);
|
|
577
|
-
if (!res.headersSent) {
|
|
578
|
-
res.writeHead(500);
|
|
579
|
-
res.end("Internal Server Error");
|
|
580
|
-
}
|
|
581
|
-
});
|
|
582
|
-
});
|
|
583
|
-
server.listen(config.port, () => {
|
|
584
|
-
console.log(`Trafic agent listening on port ${config.port}`);
|
|
585
|
-
console.log(`TLD: ${config.tld}`);
|
|
586
|
-
console.log(`Projects: ${projectList.size}`);
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
export {
|
|
590
|
-
watchProjectList as A,
|
|
591
|
-
cleanOldLogs as a,
|
|
592
|
-
buildHostnameIndex as b,
|
|
593
|
-
checkAuth as c,
|
|
594
|
-
closeDb as d,
|
|
595
|
-
getDb as e,
|
|
596
|
-
getIdleProjects as f,
|
|
597
|
-
getAccessLogs as g,
|
|
598
|
-
getIdleTimeoutMs as h,
|
|
599
|
-
getProject as i,
|
|
600
|
-
getProjectFromHostname as j,
|
|
601
|
-
getProjectInfo as k,
|
|
602
|
-
initDb as l,
|
|
603
|
-
listProjects as m,
|
|
604
|
-
loadProjectConfig as n,
|
|
605
|
-
loadProjectList as o,
|
|
606
|
-
logAccess as p,
|
|
607
|
-
matchHostname as q,
|
|
608
|
-
matchIp as r,
|
|
609
|
-
parseBasicAuth as s,
|
|
610
|
-
parseBearerToken as t,
|
|
611
|
-
setProjectStatus as u,
|
|
612
|
-
shouldNeverStop as v,
|
|
613
|
-
startProject as w,
|
|
614
|
-
startServer as x,
|
|
615
|
-
stopProject as y,
|
|
616
|
-
updateProjectAccess as z
|
|
617
|
-
};
|
|
618
|
-
//# sourceMappingURL=server-CHT47EXr.js.map
|