@studiometa/trafic-agent 0.1.26 → 0.1.28

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 CHANGED
@@ -92,6 +92,12 @@ idle_timeout = "4h"
92
92
  [auth]
93
93
  default_policy = "basic" # allow, deny, basic, or token
94
94
 
95
+ # Proxies in front of the agent (default: 1 = ddev-router/Traefik alone).
96
+ # Raise by one for each extra proxy: a CDN or load balancer ahead of
97
+ # Traefik makes it 2. Set this before using allowed_ips — it decides which
98
+ # X-Forwarded-For entry is treated as the client address.
99
+ trusted_proxy_hops = 1
100
+
95
101
  # IP whitelist (bypasses auth)
96
102
  allowed_ips = ["192.168.1.0/24", "10.0.0.0/8"]
97
103
 
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { _ as validateConfig, c as info, d as success, f as warn, h as loadConfig, i as runPendingMigrations, l as isRoot, n as setup, p as startIdleScheduler, r as listMigrations, s as exec, t as audit, u as step } from "./setup-4TI2lreq.js";
3
- import { o as closeDb, t as startServer } from "./server-VlzPosuk.js";
2
+ import { _ as validateConfig, c as info, d as success, f as warn, h as loadConfig, i as runPendingMigrations, l as isRoot, n as setup, p as startIdleScheduler, r as listMigrations, s as exec, t as audit, u as step } from "./setup-BiaJHMQW.js";
3
+ import { o as closeDb, t as startServer } from "./server-zVWzPuj2.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.26";
111
+ const current = "0.1.28";
112
112
  info(`Current version: ${current}`);
113
113
  const latest = fetchLatestVersion();
114
114
  if (!latest) warn("Could not reach npm registry — skipping version check");
@@ -155,9 +155,12 @@ Setup options:
155
155
  --tld <domain> TLD for DDEV projects (required on first run, reused from config on re-runs)
156
156
  --email <email> Email for Let's Encrypt certificates
157
157
  --no-hardening Skip server hardening steps
158
+ --no-root-ssh Disable root SSH login (recovery via rescue mode only)
158
159
  --no-docker Skip Docker installation
159
160
  --no-ddev Skip DDEV installation
160
161
  --ssh-users <users> Comma-separated list of SSH users to allow (default: ddev)
162
+ --trusted-proxy-hops <n> Proxies in front of the agent (default: 1; 2 behind a CDN).
163
+ Written to the generated config; ignored if one exists
161
164
  --dry-run Show what would be done without making changes
162
165
 
163
166
  Upgrade/update options:
@@ -171,6 +174,7 @@ Examples:
171
174
 
172
175
  # Setup a new server
173
176
  sudo trafic-agent setup --tld=previews.example.com
177
+ sudo trafic-agent setup --tld=previews.example.com --trusted-proxy-hops=2
174
178
  sudo trafic-agent setup --tld=previews.example.com --email=admin@example.com
175
179
  sudo trafic-agent setup --tld=previews.example.com --no-hardening --dry-run
176
180
 
@@ -187,7 +191,7 @@ function printHelp() {
187
191
  console.log(HELP.trim());
188
192
  }
189
193
  function printVersion() {
190
- console.log("0.1.26");
194
+ console.log("0.1.28");
191
195
  }
192
196
  async function runStart(values) {
193
197
  const config = loadConfig(values.config);
@@ -227,6 +231,20 @@ function handleUpgrade(values) {
227
231
  runUpgrade(dryRun ?? false, reExecArgs);
228
232
  if (!dryRun) console.log("\n\x1B[32m✓ Upgrade complete!\x1B[0m\n");
229
233
  }
234
+ /**
235
+ * Parse --trusted-proxy-hops, failing loudly rather than writing a config
236
+ * that would quietly break the IP allowlist.
237
+ */
238
+ function parseTrustedProxyHops(raw) {
239
+ if (raw === void 0) return;
240
+ const hops = Number(raw);
241
+ if (!Number.isInteger(hops) || hops < 1) {
242
+ console.error(`Error: --trusted-proxy-hops must be a whole number of 1 or more, got "${raw}"`);
243
+ console.error(" 1 = ddev-router/Traefik alone, 2 = a CDN in front of it");
244
+ process.exit(1);
245
+ }
246
+ return hops;
247
+ }
230
248
  async function runSetup(values) {
231
249
  const existingConfig = loadConfig();
232
250
  const tld = values.tld ?? (existingConfig.tld || void 0);
@@ -237,8 +255,10 @@ async function runSetup(values) {
237
255
  }
238
256
  await setup({
239
257
  tld,
258
+ trustedProxyHops: parseTrustedProxyHops(values["trusted-proxy-hops"]),
240
259
  email: values.email,
241
260
  noHardening: values["no-hardening"],
261
+ noRootSsh: values["no-root-ssh"],
242
262
  noDocker: values["no-docker"],
243
263
  noDdev: values["no-ddev"],
244
264
  sshUsers: values["ssh-users"] ? values["ssh-users"].split(",").map((s) => s.trim()) : void 0,
@@ -264,9 +284,11 @@ async function main() {
264
284
  tld: { type: "string" },
265
285
  email: { type: "string" },
266
286
  "no-hardening": { type: "boolean" },
287
+ "no-root-ssh": { type: "boolean" },
267
288
  "no-docker": { type: "boolean" },
268
289
  "no-ddev": { type: "boolean" },
269
290
  "ssh-users": { type: "string" },
291
+ "trusted-proxy-hops": { type: "string" },
270
292
  "dry-run": { type: "boolean" },
271
293
  list: { type: "boolean" }
272
294
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../src/setup/upgrade.ts","../src/cli.ts"],"sourcesContent":["import { execSync, spawnSync } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { step, success, info, warn, exec, isRoot } from \"./steps.js\";\nimport { runPendingMigrations } from \"./migrations/index.js\";\n\ndeclare const __VERSION__: string;\n\n/**\n * Fetch the latest published version of @studiometa/trafic-agent from the npm registry.\n * Returns null if the registry is unreachable.\n */\nexport function fetchLatestVersion(): string | null {\n try {\n return execSync(\"npm show @studiometa/trafic-agent version\", {\n encoding: \"utf-8\",\n stdio: \"pipe\",\n }).trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Compare two semver strings. Returns true if `next` is strictly greater than `current`.\n * Handles simple MAJOR.MINOR.PATCH without pre-release suffixes.\n */\nexport function isNewer(current: string, next: string): boolean {\n const parse = (v: string): [number, number, number] => {\n const parts = v.replace(/^v/, \"\").split(\".\").map(Number);\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];\n };\n\n const [cMaj, cMin, cPat] = parse(current);\n const [nMaj, nMin, nPat] = parse(next);\n\n if (nMaj !== cMaj) return nMaj > cMaj;\n if (nMin !== cMin) return nMin > cMin;\n return nPat > cPat;\n}\n\n/**\n * Install the latest version of @studiometa/trafic-agent globally via npm.\n * Uses --prefer-online to bypass the local npm cache and always fetch the\n * latest published tarball.\n */\nexport function installLatestAgent(dryRun: boolean): void {\n exec(\"npm install -g @studiometa/trafic-agent@latest --prefer-online\", { silent: !dryRun });\n}\n\n/**\n * Read the actual installed version of @studiometa/trafic-agent from its\n * package.json on disk — not from __VERSION__ which is baked in at build time.\n *\n * Resolves the package root by walking up from the trafic-agent binary:\n * /usr/bin/trafic-agent -> /usr/lib/node_modules/@studiometa/trafic-agent/dist/cli.js\n * -> /usr/lib/node_modules/@studiometa/trafic-agent/package.json\n *\n * Returns null if the file can't be read.\n */\nexport function getInstalledVersion(): string | null {\n try {\n // Resolve the real path of the binary (follow symlinks)\n const realBinary = execSync(\"readlink -f $(which trafic-agent)\", {\n encoding: \"utf-8\",\n stdio: \"pipe\",\n }).trim();\n // dist/cli.js -> up two dirs -> package root\n const pkgJson = `${realBinary.replace(/\\/dist\\/cli\\.js$/, \"\")}/package.json`;\n const pkg = JSON.parse(readFileSync(pkgJson, \"utf-8\")) as { version: string };\n return pkg.version;\n } catch {\n return null;\n }\n}\n\n/**\n * Re-exec the newly installed trafic-agent binary with the same arguments.\n * Used after a self-update so the new binary runs its own migrations.\n * This function never returns — it replaces the current process.\n */\nexport function reExecNewBinary(args: string[]): never {\n const binary = execSync(\"which trafic-agent\", { encoding: \"utf-8\", stdio: \"pipe\" }).trim();\n const result = spawnSync(binary, args, {\n stdio: \"inherit\",\n env: { ...process.env, TRAFIC_UPGRADE_REEXEC: \"1\" },\n });\n process.exit(result.status ?? 0);\n}\n\n/**\n * Restart the trafic-agent systemd service.\n */\nexport function restartAgentService(dryRun: boolean): void {\n exec(\"systemctl restart trafic-agent\", { silent: !dryRun });\n}\n\n/**\n * Full upgrade sequence:\n * 1. Check for a new version on npm\n * 2. Install it globally if one is found — then re-exec the new binary\n * so that steps 3 and 4 run with the new migration registry\n * 3. Run pending migrations\n * 4. Restart the systemd service\n */\nexport function runUpgrade(dryRun = false, reExecArgs?: string[]): void {\n if (!isRoot() && !dryRun) {\n console.error(\" \\x1b[31m✗\\x1b[0m This command must be run as root\");\n console.log(\" Run: sudo trafic-agent upgrade\");\n process.exit(1);\n }\n\n // Guard against infinite re-exec loops: only re-exec once per upgrade run.\n const alreadyReExeced = process.env[\"TRAFIC_UPGRADE_REEXEC\"] === \"1\";\n\n // ── Step 1: Check for updates ─────────────────────────────────────────────\n step(\"Check for updates\");\n\n const current = __VERSION__;\n info(`Current version: ${current}`);\n\n const latest = fetchLatestVersion();\n\n if (!latest) {\n warn(\"Could not reach npm registry — skipping version check\");\n } else if (isNewer(current, latest)) {\n success(`New version available: ${latest}`);\n\n // ── Step 2: Install ───────────────────────────────────────────────────\n step(\"Install latest version\");\n installLatestAgent(dryRun);\n\n if (!dryRun) {\n // Verify the installed version actually changed before re-execing —\n // npm can serve stale cache and leave the old binary in place.\n const installedVersion = getInstalledVersion();\n\n if (!alreadyReExeced && installedVersion && isNewer(current, installedVersion)) {\n success(`Installed @studiometa/trafic-agent@${installedVersion}`);\n // Re-exec the newly installed binary so steps 3 and 4 run with the\n // new migration registry — the current process only knows about\n // migrations that existed at the time it was compiled.\n reExecNewBinary(reExecArgs ?? [\"upgrade\"]);\n } else if (installedVersion) {\n success(`Installed @studiometa/trafic-agent@${installedVersion}`);\n }\n }\n } else {\n success(`Already up to date (${current})`);\n }\n\n // ── Step 3: Run pending migrations ───────────────────────────────────────\n step(\"Run pending migrations\");\n runPendingMigrations(dryRun);\n\n // ── Step 4: Restart service ───────────────────────────────────────────────\n step(\"Restart trafic-agent service\");\n restartAgentService(dryRun);\n if (!dryRun) {\n success(\"Service restarted\");\n }\n}\n","#!/usr/bin/env node\n\nimport { parseArgs } from \"node:util\";\nimport { loadConfig, validateConfig } from \"./utils/config.js\";\nimport { startServer } from \"./server.js\";\nimport { startIdleScheduler } from \"./tasks/stop-idle.js\";\nimport { closeDb } from \"./utils/db.js\";\nimport { setup, audit } from \"./setup/index.js\";\nimport { listMigrations } from \"./setup/migrations/index.js\";\nimport { runUpgrade } from \"./setup/upgrade.js\";\n\ndeclare const __VERSION__: string;\n\nconst HELP = `\ntrafic-agent — Server agent for DDEV preview environments\n\nUsage:\n trafic-agent <command> [options]\n\nCommands:\n start Start the agent server\n setup Setup a new server (Docker, DDEV, hardening)\n upgrade Check for a new version, install it, run migrations, restart the service\n update Alias for upgrade\n audit Run security audit checks\n version Show version\n help Show this help\n\nStart options:\n -c, --config <path> Path to config file (default: /etc/trafic/config.toml)\n -p, --port <port> Override port from config\n\nSetup options:\n --tld <domain> TLD for DDEV projects (required on first run, reused from config on re-runs)\n --email <email> Email for Let's Encrypt certificates\n --no-hardening Skip server hardening steps\n --no-docker Skip Docker installation\n --no-ddev Skip DDEV installation\n --ssh-users <users> Comma-separated list of SSH users to allow (default: ddev)\n --dry-run Show what would be done without making changes\n\nUpgrade/update options:\n --dry-run Show what would be done without making changes\n --list List all migrations and their status (no install or restart)\n\nExamples:\n # Start the agent\n trafic-agent start\n trafic-agent start --config /path/to/config.toml\n\n # Setup a new server\n sudo trafic-agent setup --tld=previews.example.com\n sudo trafic-agent setup --tld=previews.example.com --email=admin@example.com\n sudo trafic-agent setup --tld=previews.example.com --no-hardening --dry-run\n\n # Upgrade: install latest version, run migrations, restart service\n sudo trafic-agent upgrade\n sudo trafic-agent upgrade --dry-run\n trafic-agent upgrade --list\n sudo trafic-agent update # alias for upgrade\n\n # Run security audit\n trafic-agent audit\n`;\n\nfunction printHelp(): void {\n console.log(HELP.trim());\n}\n\nfunction printVersion(): void {\n console.log(__VERSION__);\n}\n\nasync function runStart(values: Record<string, unknown>): Promise<void> {\n // Load config\n const config = loadConfig(values.config as string | undefined);\n\n // Override port if specified\n if (values.port) {\n config.port = parseInt(values.port as string, 10);\n }\n\n // Validate config\n const errors = validateConfig(config);\n if (errors.length > 0) {\n console.error(\"Configuration errors:\");\n for (const error of errors) {\n console.error(` - ${error}`);\n }\n process.exit(1);\n }\n\n // Graceful shutdown\n const shutdown = (): void => {\n console.log(\"\\nShutting down...\");\n closeDb();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Start server and scheduler\n startServer(config);\n startIdleScheduler(config);\n}\n\nfunction handleUpgrade(values: Record<string, unknown>): void {\n const dryRun = values[\"dry-run\"] as boolean | undefined;\n const list = values.list as boolean | undefined;\n\n if (list) {\n const entries = listMigrations();\n console.log(\"\\nMigrations:\\n\");\n\n for (const { migration, status } of entries) {\n const icon = status === \"applied\" ? \"\\x1b[32m✓\\x1b[0m\" : \"\\x1b[36m→\\x1b[0m\";\n const label = status === \"applied\" ? \"\\x1b[90m(applied)\\x1b[0m\" : \"(pending)\";\n console.log(` ${icon} \\x1b[1m${migration.id.padEnd(30)}\\x1b[0m ${migration.description.padEnd(55)} ${label}`);\n }\n\n console.log(\"\");\n return;\n }\n\n console.log(\"\\n\\x1b[1m🚦 Trafic Server Upgrade\\x1b[0m\\n\");\n\n if (dryRun) {\n console.log(\"\\x1b[33m⚠ Dry-run mode: no changes will be made\\x1b[0m\\n\");\n }\n\n // Build re-exec args so flags survive the binary swap after a self-update\n const reExecArgs = [\"upgrade\", ...(dryRun ? [\"--dry-run\"] : [])];\n runUpgrade(dryRun ?? false, reExecArgs);\n\n if (!dryRun) {\n console.log(\"\\n\\x1b[32m✓ Upgrade complete!\\x1b[0m\\n\");\n }\n}\n\nasync function runSetup(values: Record<string, unknown>): Promise<void> {\n // Load existing config to use as defaults on re-runs\n const existingConfig = loadConfig();\n const tld = (values.tld as string | undefined) ?? (existingConfig.tld || undefined);\n\n if (!tld) {\n console.error(\"Error: --tld is required for setup\");\n console.error(\"Example: trafic-agent setup --tld=previews.example.com\");\n process.exit(1);\n }\n\n await setup({\n tld,\n email: values.email as string | undefined,\n noHardening: values[\"no-hardening\"] as boolean | undefined,\n noDocker: values[\"no-docker\"] as boolean | undefined,\n noDdev: values[\"no-ddev\"] as boolean | undefined,\n sshUsers: values[\"ssh-users\"]\n ? (values[\"ssh-users\"] as string).split(\",\").map((s) => s.trim())\n : undefined,\n dryRun: values[\"dry-run\"] as boolean | undefined,\n });\n}\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n allowPositionals: true,\n options: {\n // Common\n help: { type: \"boolean\", short: \"h\" },\n\n // Start options\n config: { type: \"string\", short: \"c\" },\n port: { type: \"string\", short: \"p\" },\n\n // Setup options\n tld: { type: \"string\" },\n email: { type: \"string\" },\n \"no-hardening\": { type: \"boolean\" },\n \"no-docker\": { type: \"boolean\" },\n \"no-ddev\": { type: \"boolean\" },\n \"ssh-users\": { type: \"string\" },\n \"dry-run\": { type: \"boolean\" },\n\n // Upgrade options\n list: { type: \"boolean\" },\n },\n });\n\n const command = positionals[0] ?? \"help\";\n\n if (values.help) {\n printHelp();\n process.exit(0);\n }\n\n switch (command) {\n case \"help\":\n printHelp();\n break;\n\n case \"version\":\n printVersion();\n break;\n\n case \"start\":\n await runStart(values);\n break;\n\n case \"setup\":\n await runSetup(values);\n break;\n\n case \"upgrade\":\n case \"update\":\n handleUpgrade(values);\n break;\n\n case \"audit\":\n await audit();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n printHelp();\n process.exit(1);\n }\n}\n\nmain().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../src/setup/upgrade.ts","../src/cli.ts"],"sourcesContent":["import { execSync, spawnSync } from \"node:child_process\";\nimport { readFileSync } from \"node:fs\";\nimport { step, success, info, warn, exec, isRoot } from \"./steps.js\";\nimport { runPendingMigrations } from \"./migrations/index.js\";\n\ndeclare const __VERSION__: string;\n\n/**\n * Fetch the latest published version of @studiometa/trafic-agent from the npm registry.\n * Returns null if the registry is unreachable.\n */\nexport function fetchLatestVersion(): string | null {\n try {\n return execSync(\"npm show @studiometa/trafic-agent version\", {\n encoding: \"utf-8\",\n stdio: \"pipe\",\n }).trim();\n } catch {\n return null;\n }\n}\n\n/**\n * Compare two semver strings. Returns true if `next` is strictly greater than `current`.\n * Handles simple MAJOR.MINOR.PATCH without pre-release suffixes.\n */\nexport function isNewer(current: string, next: string): boolean {\n const parse = (v: string): [number, number, number] => {\n const parts = v.replace(/^v/, \"\").split(\".\").map(Number);\n return [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];\n };\n\n const [cMaj, cMin, cPat] = parse(current);\n const [nMaj, nMin, nPat] = parse(next);\n\n if (nMaj !== cMaj) return nMaj > cMaj;\n if (nMin !== cMin) return nMin > cMin;\n return nPat > cPat;\n}\n\n/**\n * Install the latest version of @studiometa/trafic-agent globally via npm.\n * Uses --prefer-online to bypass the local npm cache and always fetch the\n * latest published tarball.\n */\nexport function installLatestAgent(dryRun: boolean): void {\n exec(\"npm install -g @studiometa/trafic-agent@latest --prefer-online\", { silent: !dryRun });\n}\n\n/**\n * Read the actual installed version of @studiometa/trafic-agent from its\n * package.json on disk — not from __VERSION__ which is baked in at build time.\n *\n * Resolves the package root by walking up from the trafic-agent binary:\n * /usr/bin/trafic-agent -> /usr/lib/node_modules/@studiometa/trafic-agent/dist/cli.js\n * -> /usr/lib/node_modules/@studiometa/trafic-agent/package.json\n *\n * Returns null if the file can't be read.\n */\nexport function getInstalledVersion(): string | null {\n try {\n // Resolve the real path of the binary (follow symlinks)\n const realBinary = execSync(\"readlink -f $(which trafic-agent)\", {\n encoding: \"utf-8\",\n stdio: \"pipe\",\n }).trim();\n // dist/cli.js -> up two dirs -> package root\n const pkgJson = `${realBinary.replace(/\\/dist\\/cli\\.js$/, \"\")}/package.json`;\n const pkg = JSON.parse(readFileSync(pkgJson, \"utf-8\")) as { version: string };\n return pkg.version;\n } catch {\n return null;\n }\n}\n\n/**\n * Re-exec the newly installed trafic-agent binary with the same arguments.\n * Used after a self-update so the new binary runs its own migrations.\n * This function never returns — it replaces the current process.\n */\nexport function reExecNewBinary(args: string[]): never {\n const binary = execSync(\"which trafic-agent\", { encoding: \"utf-8\", stdio: \"pipe\" }).trim();\n const result = spawnSync(binary, args, {\n stdio: \"inherit\",\n env: { ...process.env, TRAFIC_UPGRADE_REEXEC: \"1\" },\n });\n process.exit(result.status ?? 0);\n}\n\n/**\n * Restart the trafic-agent systemd service.\n */\nexport function restartAgentService(dryRun: boolean): void {\n exec(\"systemctl restart trafic-agent\", { silent: !dryRun });\n}\n\n/**\n * Full upgrade sequence:\n * 1. Check for a new version on npm\n * 2. Install it globally if one is found — then re-exec the new binary\n * so that steps 3 and 4 run with the new migration registry\n * 3. Run pending migrations\n * 4. Restart the systemd service\n */\nexport function runUpgrade(dryRun = false, reExecArgs?: string[]): void {\n if (!isRoot() && !dryRun) {\n console.error(\" \\x1b[31m✗\\x1b[0m This command must be run as root\");\n console.log(\" Run: sudo trafic-agent upgrade\");\n process.exit(1);\n }\n\n // Guard against infinite re-exec loops: only re-exec once per upgrade run.\n const alreadyReExeced = process.env[\"TRAFIC_UPGRADE_REEXEC\"] === \"1\";\n\n // ── Step 1: Check for updates ─────────────────────────────────────────────\n step(\"Check for updates\");\n\n const current = __VERSION__;\n info(`Current version: ${current}`);\n\n const latest = fetchLatestVersion();\n\n if (!latest) {\n warn(\"Could not reach npm registry — skipping version check\");\n } else if (isNewer(current, latest)) {\n success(`New version available: ${latest}`);\n\n // ── Step 2: Install ───────────────────────────────────────────────────\n step(\"Install latest version\");\n installLatestAgent(dryRun);\n\n if (!dryRun) {\n // Verify the installed version actually changed before re-execing —\n // npm can serve stale cache and leave the old binary in place.\n const installedVersion = getInstalledVersion();\n\n if (!alreadyReExeced && installedVersion && isNewer(current, installedVersion)) {\n success(`Installed @studiometa/trafic-agent@${installedVersion}`);\n // Re-exec the newly installed binary so steps 3 and 4 run with the\n // new migration registry — the current process only knows about\n // migrations that existed at the time it was compiled.\n reExecNewBinary(reExecArgs ?? [\"upgrade\"]);\n } else if (installedVersion) {\n success(`Installed @studiometa/trafic-agent@${installedVersion}`);\n }\n }\n } else {\n success(`Already up to date (${current})`);\n }\n\n // ── Step 3: Run pending migrations ───────────────────────────────────────\n step(\"Run pending migrations\");\n runPendingMigrations(dryRun);\n\n // ── Step 4: Restart service ───────────────────────────────────────────────\n step(\"Restart trafic-agent service\");\n restartAgentService(dryRun);\n if (!dryRun) {\n success(\"Service restarted\");\n }\n}\n","#!/usr/bin/env node\n\nimport { parseArgs } from \"node:util\";\nimport { loadConfig, validateConfig } from \"./utils/config.js\";\nimport { startServer } from \"./server.js\";\nimport { startIdleScheduler } from \"./tasks/stop-idle.js\";\nimport { closeDb } from \"./utils/db.js\";\nimport { setup, audit } from \"./setup/index.js\";\nimport { listMigrations } from \"./setup/migrations/index.js\";\nimport { runUpgrade } from \"./setup/upgrade.js\";\n\ndeclare const __VERSION__: string;\n\nconst HELP = `\ntrafic-agent — Server agent for DDEV preview environments\n\nUsage:\n trafic-agent <command> [options]\n\nCommands:\n start Start the agent server\n setup Setup a new server (Docker, DDEV, hardening)\n upgrade Check for a new version, install it, run migrations, restart the service\n update Alias for upgrade\n audit Run security audit checks\n version Show version\n help Show this help\n\nStart options:\n -c, --config <path> Path to config file (default: /etc/trafic/config.toml)\n -p, --port <port> Override port from config\n\nSetup options:\n --tld <domain> TLD for DDEV projects (required on first run, reused from config on re-runs)\n --email <email> Email for Let's Encrypt certificates\n --no-hardening Skip server hardening steps\n --no-root-ssh Disable root SSH login (recovery via rescue mode only)\n --no-docker Skip Docker installation\n --no-ddev Skip DDEV installation\n --ssh-users <users> Comma-separated list of SSH users to allow (default: ddev)\n --trusted-proxy-hops <n> Proxies in front of the agent (default: 1; 2 behind a CDN).\n Written to the generated config; ignored if one exists\n --dry-run Show what would be done without making changes\n\nUpgrade/update options:\n --dry-run Show what would be done without making changes\n --list List all migrations and their status (no install or restart)\n\nExamples:\n # Start the agent\n trafic-agent start\n trafic-agent start --config /path/to/config.toml\n\n # Setup a new server\n sudo trafic-agent setup --tld=previews.example.com\n sudo trafic-agent setup --tld=previews.example.com --trusted-proxy-hops=2\n sudo trafic-agent setup --tld=previews.example.com --email=admin@example.com\n sudo trafic-agent setup --tld=previews.example.com --no-hardening --dry-run\n\n # Upgrade: install latest version, run migrations, restart service\n sudo trafic-agent upgrade\n sudo trafic-agent upgrade --dry-run\n trafic-agent upgrade --list\n sudo trafic-agent update # alias for upgrade\n\n # Run security audit\n trafic-agent audit\n`;\n\nfunction printHelp(): void {\n console.log(HELP.trim());\n}\n\nfunction printVersion(): void {\n console.log(__VERSION__);\n}\n\nasync function runStart(values: Record<string, unknown>): Promise<void> {\n // Load config\n const config = loadConfig(values.config as string | undefined);\n\n // Override port if specified\n if (values.port) {\n config.port = parseInt(values.port as string, 10);\n }\n\n // Validate config\n const errors = validateConfig(config);\n if (errors.length > 0) {\n console.error(\"Configuration errors:\");\n for (const error of errors) {\n console.error(` - ${error}`);\n }\n process.exit(1);\n }\n\n // Graceful shutdown\n const shutdown = (): void => {\n console.log(\"\\nShutting down...\");\n closeDb();\n process.exit(0);\n };\n\n process.on(\"SIGINT\", shutdown);\n process.on(\"SIGTERM\", shutdown);\n\n // Start server and scheduler\n startServer(config);\n startIdleScheduler(config);\n}\n\nfunction handleUpgrade(values: Record<string, unknown>): void {\n const dryRun = values[\"dry-run\"] as boolean | undefined;\n const list = values.list as boolean | undefined;\n\n if (list) {\n const entries = listMigrations();\n console.log(\"\\nMigrations:\\n\");\n\n for (const { migration, status } of entries) {\n const icon = status === \"applied\" ? \"\\x1b[32m✓\\x1b[0m\" : \"\\x1b[36m→\\x1b[0m\";\n const label = status === \"applied\" ? \"\\x1b[90m(applied)\\x1b[0m\" : \"(pending)\";\n console.log(` ${icon} \\x1b[1m${migration.id.padEnd(30)}\\x1b[0m ${migration.description.padEnd(55)} ${label}`);\n }\n\n console.log(\"\");\n return;\n }\n\n console.log(\"\\n\\x1b[1m🚦 Trafic Server Upgrade\\x1b[0m\\n\");\n\n if (dryRun) {\n console.log(\"\\x1b[33m⚠ Dry-run mode: no changes will be made\\x1b[0m\\n\");\n }\n\n // Build re-exec args so flags survive the binary swap after a self-update\n const reExecArgs = [\"upgrade\", ...(dryRun ? [\"--dry-run\"] : [])];\n runUpgrade(dryRun ?? false, reExecArgs);\n\n if (!dryRun) {\n console.log(\"\\n\\x1b[32m✓ Upgrade complete!\\x1b[0m\\n\");\n }\n}\n\n/**\n * Parse --trusted-proxy-hops, failing loudly rather than writing a config\n * that would quietly break the IP allowlist.\n */\nfunction parseTrustedProxyHops(raw?: string): number | undefined {\n if (raw === undefined) {\n return undefined;\n }\n\n const hops = Number(raw);\n\n if (!Number.isInteger(hops) || hops < 1) {\n console.error(\n `Error: --trusted-proxy-hops must be a whole number of 1 or more, got \"${raw}\"`,\n );\n console.error(\" 1 = ddev-router/Traefik alone, 2 = a CDN in front of it\");\n process.exit(1);\n }\n\n return hops;\n}\n\nasync function runSetup(values: Record<string, unknown>): Promise<void> {\n // Load existing config to use as defaults on re-runs\n const existingConfig = loadConfig();\n const tld = (values.tld as string | undefined) ?? (existingConfig.tld || undefined);\n\n if (!tld) {\n console.error(\"Error: --tld is required for setup\");\n console.error(\"Example: trafic-agent setup --tld=previews.example.com\");\n process.exit(1);\n }\n\n await setup({\n tld,\n trustedProxyHops: parseTrustedProxyHops(values[\"trusted-proxy-hops\"] as string | undefined),\n email: values.email as string | undefined,\n noHardening: values[\"no-hardening\"] as boolean | undefined,\n noRootSsh: values[\"no-root-ssh\"] as boolean | undefined,\n noDocker: values[\"no-docker\"] as boolean | undefined,\n noDdev: values[\"no-ddev\"] as boolean | undefined,\n sshUsers: values[\"ssh-users\"]\n ? (values[\"ssh-users\"] as string).split(\",\").map((s) => s.trim())\n : undefined,\n dryRun: values[\"dry-run\"] as boolean | undefined,\n });\n}\n\nasync function main(): Promise<void> {\n const { values, positionals } = parseArgs({\n allowPositionals: true,\n options: {\n // Common\n help: { type: \"boolean\", short: \"h\" },\n\n // Start options\n config: { type: \"string\", short: \"c\" },\n port: { type: \"string\", short: \"p\" },\n\n // Setup options\n tld: { type: \"string\" },\n email: { type: \"string\" },\n \"no-hardening\": { type: \"boolean\" },\n \"no-root-ssh\": { type: \"boolean\" },\n \"no-docker\": { type: \"boolean\" },\n \"no-ddev\": { type: \"boolean\" },\n \"ssh-users\": { type: \"string\" },\n \"trusted-proxy-hops\": { type: \"string\" },\n \"dry-run\": { type: \"boolean\" },\n\n // Upgrade options\n list: { type: \"boolean\" },\n },\n });\n\n const command = positionals[0] ?? \"help\";\n\n if (values.help) {\n printHelp();\n process.exit(0);\n }\n\n switch (command) {\n case \"help\":\n printHelp();\n break;\n\n case \"version\":\n printVersion();\n break;\n\n case \"start\":\n await runStart(values);\n break;\n\n case \"setup\":\n await runSetup(values);\n break;\n\n case \"upgrade\":\n case \"update\":\n handleUpgrade(values);\n break;\n\n case \"audit\":\n await audit();\n break;\n\n default:\n console.error(`Unknown command: ${command}`);\n printHelp();\n process.exit(1);\n }\n}\n\nmain().catch((error) => {\n console.error(\"Fatal error:\", error);\n process.exit(1);\n});\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { _ as validateConfig, a as printAuditResults, g as parseDuration, h as loadConfig, m as stopIdleProjects, n as setup, o as runAudit, p as startIdleScheduler, t as audit } from "./setup-4TI2lreq.js";
2
- import { C as checkAuth, D as parseBearerToken, E as parseBasicAuth, S as watchProjectList, T as matchIp, _ as getProjectInfo, a as cleanOldLogs, b as startProject, c as getDb, d as initDb, f as logAccess, g as getProjectFromHostname, h as buildHostnameIndex, i as shouldNeverStop, l as getIdleProjects, m as updateProjectAccess, n as getIdleTimeoutMs, o as closeDb, p as setProjectStatus, r as loadProjectConfig, s as getAccessLogs, t as startServer, u as getProject, v as listProjects, w as matchHostname, x as stopProject, y as loadProjectList } from "./server-VlzPosuk.js";
1
+ import { _ as validateConfig, a as printAuditResults, g as parseDuration, h as loadConfig, m as stopIdleProjects, n as setup, o as runAudit, p as startIdleScheduler, t as audit } from "./setup-BiaJHMQW.js";
2
+ import { C as checkAuth, D as parseBearerToken, E as parseBasicAuth, S as watchProjectList, T as matchIp, _ as getProjectInfo, a as cleanOldLogs, b as startProject, c as getDb, d as initDb, f as logAccess, g as getProjectFromHostname, h as buildHostnameIndex, i as shouldNeverStop, l as getIdleProjects, m as updateProjectAccess, n as getIdleTimeoutMs, o as closeDb, p as setProjectStatus, r as loadProjectConfig, s as getAccessLogs, t as startServer, u as getProject, v as listProjects, w as matchHostname, x as stopProject, y as loadProjectList } from "./server-zVWzPuj2.js";
3
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 };
@@ -73,11 +73,32 @@ function parseBearerToken(header) {
73
73
  return header.slice(7);
74
74
  }
75
75
  /**
76
+ * Resolve the client address from the socket peer and X-Forwarded-For.
77
+ *
78
+ * Every proxy appends the address it saw, so the entries grow left to right
79
+ * and the rightmost ones were added by the proxies closest to us. Counting
80
+ * `trustedHops` from the right therefore lands on the client, while anything
81
+ * further left is whatever the client sent and must be ignored.
82
+ *
83
+ * Reading the leftmost entry instead — as this used to — let a client set
84
+ * `X-Forwarded-For: <allowed-ip>` and match the IP allowlist, which grants
85
+ * access unconditionally.
86
+ *
87
+ * Falls back to the socket peer whenever the header cannot be trusted to
88
+ * hold that many entries.
89
+ */
90
+ function resolveClientIp(socketIp, forwardedFor, trustedHops = 1) {
91
+ if (!forwardedFor || trustedHops < 1) return socketIp;
92
+ const entries = forwardedFor.split(",").map((entry) => entry.trim()).filter(Boolean);
93
+ if (entries.length < trustedHops) return socketIp;
94
+ return entries[entries.length - trustedHops] ?? socketIp;
95
+ }
96
+ /**
76
97
  * Check authentication for a request
77
98
  */
78
99
  function checkAuth(request, config) {
79
100
  const { hostname, ip, authorization } = request;
80
- const realIp = request.forwardedFor?.split(",")[0].trim() ?? ip;
101
+ const realIp = resolveClientIp(ip, request.forwardedFor, config.trustedProxyHops);
81
102
  for (const pattern of config.allowedIps) if (matchIp(realIp, pattern)) return {
82
103
  allowed: true,
83
104
  reason: "ip"
@@ -550,23 +571,25 @@ function getEffectiveAuthConfig(projectName) {
550
571
  */
551
572
  function handleAuth(req, res) {
552
573
  const hostname = req.headers["x-forwarded-host"] ?? "";
553
- const ip = req.headers["x-forwarded-for"] ?? req.socket.remoteAddress ?? "";
574
+ const socketIp = req.socket.remoteAddress ?? "";
575
+ const forwardedFor = req.headers["x-forwarded-for"];
554
576
  const authorization = req.headers["authorization"];
555
577
  const path = req.headers["x-forwarded-uri"] ?? "/";
556
578
  const projectName = hostnameIndex.get(hostname);
557
579
  const authConfig = getEffectiveAuthConfig(projectName);
580
+ const clientIp = resolveClientIp(socketIp, forwardedFor, authConfig.trustedProxyHops);
558
581
  if (checkAuth({
559
582
  hostname,
560
- ip: ip.split(",")[0].trim(),
583
+ ip: socketIp,
561
584
  authorization,
562
- forwardedFor: ip
585
+ forwardedFor
563
586
  }, authConfig).allowed) {
564
587
  if (projectName) {
565
588
  updateProjectAccess(projectName);
566
589
  logAccess({
567
590
  project: projectName,
568
591
  timestamp: Date.now(),
569
- ip: ip.split(",")[0].trim(),
592
+ ip: clientIp,
570
593
  userAgent: req.headers["user-agent"] ?? "",
571
594
  path
572
595
  });
@@ -696,4 +719,4 @@ function startServer(agentConfig) {
696
719
  //#endregion
697
720
  export { checkAuth as C, parseBearerToken as D, parseBasicAuth as E, watchProjectList as S, matchIp as T, getProjectInfo as _, cleanOldLogs as a, startProject as b, getDb as c, initDb as d, logAccess as f, getProjectFromHostname as g, buildHostnameIndex as h, shouldNeverStop as i, getIdleProjects as l, updateProjectAccess as m, getIdleTimeoutMs as n, closeDb as o, setProjectStatus as p, loadProjectConfig as r, getAccessLogs as s, startServer as t, getProject as u, listProjects as v, matchHostname as w, stopProject as x, loadProjectList as y };
698
721
 
699
- //# sourceMappingURL=server-VlzPosuk.js.map
722
+ //# sourceMappingURL=server-zVWzPuj2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-zVWzPuj2.js","names":[],"sources":["../src/utils/auth.ts","../src/utils/ddev.ts","../src/utils/db.ts","../src/utils/project-config.ts","../src/server.ts"],"sourcesContent":["import type { AuthConfig, AuthRequest, AuthResult, AuthRule } from \"../types.js\";\n\n/**\n * Check if an IP matches a pattern (supports CIDR notation)\n */\nexport function matchIp(ip: string, pattern: string): boolean {\n // Exact match\n if (ip === pattern) return true;\n\n // CIDR notation\n if (pattern.includes(\"/\")) {\n return matchCidr(ip, pattern);\n }\n\n // Wildcard (e.g., 192.168.*)\n if (pattern.includes(\"*\")) {\n const regex = new RegExp(\n \"^\" + pattern.replace(/\\./g, \"\\\\.\").replace(/\\*/g, \".*\") + \"$\",\n );\n return regex.test(ip);\n }\n\n return false;\n}\n\n/**\n * Match IP against CIDR range\n */\nfunction matchCidr(ip: string, cidr: string): boolean {\n const [range, bits] = cidr.split(\"/\");\n const mask = parseInt(bits, 10);\n\n const ipNum = ipToNumber(ip);\n const rangeNum = ipToNumber(range);\n\n if (ipNum === null || rangeNum === null) return false;\n\n const maskNum = ~((1 << (32 - mask)) - 1);\n return (ipNum & maskNum) === (rangeNum & maskNum);\n}\n\n/**\n * Convert IP string to number\n */\nfunction ipToNumber(ip: string): number | null {\n const parts = ip.split(\".\");\n if (parts.length !== 4) return null;\n\n let num = 0;\n for (const part of parts) {\n const n = parseInt(part, 10);\n if (isNaN(n) || n < 0 || n > 255) return null;\n num = (num << 8) | n;\n }\n return num >>> 0; // Convert to unsigned\n}\n\n/**\n * Match hostname against a glob pattern\n */\nexport function matchHostname(hostname: string, pattern: string): boolean {\n // Exact match\n if (hostname === pattern) return true;\n\n // Glob pattern\n const regex = new RegExp(\n \"^\" +\n pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replace(/\\*/g, \".*\")\n .replace(/\\?/g, \".\") +\n \"$\",\n );\n return regex.test(hostname);\n}\n\n/**\n * Parse Basic auth header\n */\nexport function parseBasicAuth(\n header: string,\n): { username: string; password: string } | null {\n if (!header.startsWith(\"Basic \")) return null;\n\n try {\n const encoded = header.slice(6);\n const decoded = Buffer.from(encoded, \"base64\").toString(\"utf-8\");\n const colonIndex = decoded.indexOf(\":\");\n if (colonIndex === -1) return null;\n\n return {\n username: decoded.slice(0, colonIndex),\n password: decoded.slice(colonIndex + 1),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Parse Bearer token from header\n */\nexport function parseBearerToken(header: string): string | null {\n if (!header.startsWith(\"Bearer \")) return null;\n return header.slice(7);\n}\n\n/**\n * Resolve the client address from the socket peer and X-Forwarded-For.\n *\n * Every proxy appends the address it saw, so the entries grow left to right\n * and the rightmost ones were added by the proxies closest to us. Counting\n * `trustedHops` from the right therefore lands on the client, while anything\n * further left is whatever the client sent and must be ignored.\n *\n * Reading the leftmost entry instead — as this used to — let a client set\n * `X-Forwarded-For: <allowed-ip>` and match the IP allowlist, which grants\n * access unconditionally.\n *\n * Falls back to the socket peer whenever the header cannot be trusted to\n * hold that many entries.\n */\nexport function resolveClientIp(\n socketIp: string,\n forwardedFor: string | undefined,\n trustedHops = 1,\n): string {\n if (!forwardedFor || trustedHops < 1) {\n return socketIp;\n }\n\n const entries = forwardedFor\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n\n // Fewer entries than trusted proxies means the chain is shorter than\n // configured — the socket peer is the only address we can rely on.\n if (entries.length < trustedHops) {\n return socketIp;\n }\n\n return entries[entries.length - trustedHops] ?? socketIp;\n}\n\n/**\n * Check authentication for a request\n */\nexport function checkAuth(request: AuthRequest, config: AuthConfig): AuthResult {\n const { hostname, ip, authorization } = request;\n\n const realIp = resolveClientIp(ip, request.forwardedFor, config.trustedProxyHops);\n\n // Check IP whitelist first (always allows)\n for (const pattern of config.allowedIps) {\n if (matchIp(realIp, pattern)) {\n return { allowed: true, reason: \"ip\" };\n }\n }\n\n // Check per-hostname rules\n for (const rule of config.rules) {\n if (matchHostname(hostname, rule.match)) {\n return checkRule(rule, realIp, authorization, config);\n }\n }\n\n // Apply default policy\n return applyDefaultPolicy(config, realIp, authorization);\n}\n\n/**\n * Check a specific rule\n */\nfunction checkRule(\n rule: AuthRule,\n ip: string,\n authorization: string | undefined,\n config: AuthConfig,\n): AuthResult {\n // Rule-specific IP whitelist\n if (rule.allowedIps) {\n for (const pattern of rule.allowedIps) {\n if (matchIp(ip, pattern)) {\n return { allowed: true, reason: \"rule\" };\n }\n }\n }\n\n switch (rule.policy) {\n case \"allow\":\n return { allowed: true, reason: \"rule\" };\n\n case \"deny\":\n return { allowed: false, reason: \"rule\" };\n\n case \"token\": {\n if (!authorization) return { allowed: false, reason: \"rule\" };\n const token = parseBearerToken(authorization);\n const tokens = rule.tokens ?? config.tokens;\n if (token && tokens.includes(token)) {\n return { allowed: true, reason: \"token\" };\n }\n return { allowed: false, reason: \"rule\" };\n }\n\n case \"basic\": {\n if (!authorization) return { allowed: false, reason: \"rule\" };\n const creds = parseBasicAuth(authorization);\n if (creds) {\n const credString = `${creds.username}:${creds.password}`;\n if (config.basicAuth.includes(credString)) {\n return { allowed: true, reason: \"basic\" };\n }\n }\n return { allowed: false, reason: \"rule\" };\n }\n\n default:\n return { allowed: false, reason: \"rule\" };\n }\n}\n\n/**\n * Apply default policy\n */\nfunction applyDefaultPolicy(\n config: AuthConfig,\n ip: string,\n authorization: string | undefined,\n): AuthResult {\n switch (config.defaultPolicy) {\n case \"allow\":\n return { allowed: true, reason: \"default\" };\n\n case \"deny\":\n return { allowed: false, reason: \"default\" };\n\n case \"basic\": {\n if (!authorization) return { allowed: false, reason: \"default\" };\n const creds = parseBasicAuth(authorization);\n if (creds) {\n const credString = `${creds.username}:${creds.password}`;\n if (config.basicAuth.includes(credString)) {\n return { allowed: true, reason: \"basic\" };\n }\n }\n // Also check tokens as fallback\n const token = parseBearerToken(authorization);\n if (token && config.tokens.includes(token)) {\n return { allowed: true, reason: \"token\" };\n }\n return { allowed: false, reason: \"default\" };\n }\n\n case \"token\": {\n if (!authorization) return { allowed: false, reason: \"default\" };\n const token = parseBearerToken(authorization);\n if (token && config.tokens.includes(token)) {\n return { allowed: true, reason: \"token\" };\n }\n return { allowed: false, reason: \"default\" };\n }\n\n default:\n return { allowed: false, reason: \"default\" };\n }\n}\n","import { readFileSync, existsSync, watch } from \"node:fs\";\nimport { execSync } from \"node:child_process\";\nimport type { DdevProject } from \"../types.js\";\n\n// Simple YAML parser for project_list.yaml (avoid dependencies)\n// The file format is simple: key: value pairs with quoted strings\nfunction parseSimpleYaml(content: string): Record<string, string> {\n const result: Record<string, string> = {};\n const lines = content.split(\"\\n\");\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n\n const colonIndex = trimmed.indexOf(\":\");\n if (colonIndex === -1) continue;\n\n const key = trimmed.slice(0, colonIndex).trim();\n let value = trimmed.slice(colonIndex + 1).trim();\n\n // Remove quotes\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n\n result[key] = value;\n }\n\n return result;\n}\n\n/**\n * Load projects from DDEV's project_list.yaml\n * Returns a map of project name -> app root path\n */\nexport function loadProjectList(\n projectListPath: string,\n): Map<string, string> {\n if (!existsSync(projectListPath)) {\n return new Map();\n }\n\n const content = readFileSync(projectListPath, \"utf-8\");\n const parsed = parseSimpleYaml(content);\n\n return new Map(Object.entries(parsed));\n}\n\n/**\n * Build a hostname -> project name index\n */\nexport function buildHostnameIndex(\n projects: Map<string, string>,\n tld: string,\n): Map<string, string> {\n const index = new Map<string, string>();\n\n for (const [name] of projects) {\n // Primary hostname: project-name.tld\n index.set(`${name}.${tld}`, name);\n }\n\n return index;\n}\n\n/**\n * Get project name from hostname\n */\nexport function getProjectFromHostname(\n hostname: string,\n index: Map<string, string>,\n): string | undefined {\n return index.get(hostname);\n}\n\n/**\n * Watch project_list.yaml for changes\n */\nexport function watchProjectList(\n projectListPath: string,\n onChange: () => void,\n): void {\n if (!existsSync(projectListPath)) {\n console.warn(`Project list not found: ${projectListPath}`);\n return;\n }\n\n watch(projectListPath, (eventType) => {\n if (eventType === \"change\") {\n onChange();\n }\n });\n}\n\n/**\n * Get detailed project info using ddev describe\n */\nexport function getProjectInfo(name: string): DdevProject | undefined {\n try {\n const output = execSync(`ddev describe ${name} -j`, {\n encoding: \"utf-8\",\n timeout: 10000,\n });\n const data = JSON.parse(output);\n\n if (!data.raw) return undefined;\n\n const raw = data.raw;\n return {\n name: raw.name,\n status: raw.status,\n appRoot: raw.approot,\n httpURLs: raw.httpURLs ?? [],\n httpsURLs: raw.httpsURLs ?? [],\n type: raw.type,\n phpVersion: raw.php_version,\n dbType: raw.dbinfo?.dbType,\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * Start a DDEV project\n */\nexport function startProject(name: string): boolean {\n try {\n execSync(`ddev start ${name}`, {\n encoding: \"utf-8\",\n timeout: 120000, // 2 minutes\n stdio: \"pipe\",\n });\n return true;\n } catch (error) {\n console.error(`Failed to start project ${name}:`, error);\n return false;\n }\n}\n\n/**\n * Stop a DDEV project\n */\nexport function stopProject(name: string): boolean {\n try {\n execSync(`ddev stop ${name}`, {\n encoding: \"utf-8\",\n timeout: 60000, // 1 minute\n stdio: \"pipe\",\n });\n return true;\n } catch (error) {\n console.error(`Failed to stop project ${name}:`, error);\n return false;\n }\n}\n\n/**\n * List all DDEV projects with their status\n */\nexport function listProjects(): DdevProject[] {\n try {\n const output = execSync(\"ddev list -j\", {\n encoding: \"utf-8\",\n timeout: 30000,\n });\n const data = JSON.parse(output);\n\n if (!Array.isArray(data)) return [];\n\n return data.map((p: Record<string, unknown>) => ({\n name: p.name as string,\n status: p.status as DdevProject[\"status\"],\n appRoot: p.approot as string,\n httpURLs: (p.httpURLs as string[]) ?? [],\n httpsURLs: (p.httpsURLs as string[]) ?? [],\n type: p.type as string,\n }));\n } catch {\n return [];\n }\n}\n","import { DatabaseSync } from \"node:sqlite\";\nimport type { ProjectRecord, AccessLog } from \"../types.js\";\n\nlet db: DatabaseSync | null = null;\n\n/**\n * Initialize the database connection and schema\n */\nexport function initDb(dbPath: string): DatabaseSync {\n db = new DatabaseSync(dbPath);\n db.exec(\"PRAGMA journal_mode=WAL\");\n\n // Create tables\n db.exec(`\n CREATE TABLE IF NOT EXISTS projects (\n name TEXT PRIMARY KEY,\n last_access INTEGER NOT NULL,\n status TEXT NOT NULL DEFAULT 'stopped'\n );\n\n CREATE TABLE IF NOT EXISTS access_logs (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n project TEXT NOT NULL,\n timestamp INTEGER NOT NULL,\n ip TEXT NOT NULL,\n user_agent TEXT NOT NULL,\n path TEXT NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_access_logs_project ON access_logs(project);\n CREATE INDEX IF NOT EXISTS idx_access_logs_timestamp ON access_logs(timestamp);\n `);\n\n return db;\n}\n\n/**\n * Get the database instance\n */\nexport function getDb(): DatabaseSync {\n if (!db) {\n throw new Error(\"Database not initialized. Call initDb first.\");\n }\n return db;\n}\n\n/**\n * Close the database connection\n */\nexport function closeDb(): void {\n if (db) {\n db.close();\n db = null;\n }\n}\n\n/**\n * Update project last access time\n */\nexport function updateProjectAccess(name: string): void {\n const database = getDb();\n const stmt = database.prepare(`\n INSERT INTO projects (name, last_access, status)\n VALUES (?, ?, 'running')\n ON CONFLICT(name) DO UPDATE SET last_access = excluded.last_access\n `);\n stmt.run(name, Date.now());\n}\n\n/**\n * Get project record\n */\nexport function getProject(name: string): ProjectRecord | undefined {\n const database = getDb();\n const stmt = database.prepare(\n \"SELECT name, last_access as lastAccess, status FROM projects WHERE name = ?\",\n );\n return stmt.get(name) as unknown as ProjectRecord | undefined;\n}\n\n/**\n * Set project status\n */\nexport function setProjectStatus(\n name: string,\n status: ProjectRecord[\"status\"],\n): void {\n const database = getDb();\n const stmt = database.prepare(`\n INSERT INTO projects (name, last_access, status)\n VALUES (?, ?, ?)\n ON CONFLICT(name) DO UPDATE SET status = excluded.status\n `);\n stmt.run(name, Date.now(), status);\n}\n\n/**\n * Get idle projects (last access older than threshold)\n */\nexport function getIdleProjects(thresholdMs: number): ProjectRecord[] {\n const database = getDb();\n const cutoff = Date.now() - thresholdMs;\n const stmt = database.prepare(`\n SELECT name, last_access as lastAccess, status\n FROM projects\n WHERE last_access < ? AND status = 'running'\n `);\n return stmt.all(cutoff) as unknown as ProjectRecord[];\n}\n\n/**\n * Log an access\n */\nexport function logAccess(log: Omit<AccessLog, \"id\">): void {\n const database = getDb();\n const stmt = database.prepare(`\n INSERT INTO access_logs (project, timestamp, ip, user_agent, path)\n VALUES (?, ?, ?, ?, ?)\n `);\n stmt.run(log.project, log.timestamp, log.ip, log.userAgent, log.path);\n}\n\n/**\n * Get recent access logs for a project\n */\nexport function getAccessLogs(project: string, limit = 100): AccessLog[] {\n const database = getDb();\n const stmt = database.prepare(`\n SELECT id, project, timestamp, ip, user_agent as userAgent, path\n FROM access_logs\n WHERE project = ?\n ORDER BY timestamp DESC\n LIMIT ?\n `);\n return stmt.all(project, limit) as unknown as AccessLog[];\n}\n\n/**\n * Clean old access logs (older than given days)\n */\nexport function cleanOldLogs(days: number): number {\n const database = getDb();\n const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;\n const stmt = database.prepare(\"DELETE FROM access_logs WHERE timestamp < ?\");\n const result = stmt.run(cutoff);\n return Number(result.changes);\n}\n","import { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\n/**\n * Per-project Trafic configuration\n * Read from .ddev/config.trafic.yaml\n */\nexport interface ProjectConfig {\n /** Auth policy override: allow | deny | basic | token */\n auth_policy?: \"allow\" | \"deny\" | \"basic\" | \"token\";\n /** Idle timeout override: \"never\" or duration like \"30m\", \"4h\" */\n idle_timeout?: string;\n}\n\n/**\n * Simple YAML parser for config.trafic.yaml\n * Only supports simple key: value pairs (no nesting, no arrays)\n */\nfunction parseSimpleYaml(content: string): Record<string, string> {\n const result: Record<string, string> = {};\n const lines = content.split(\"\\n\");\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Skip empty lines and comments\n if (!trimmed || trimmed.startsWith(\"#\")) continue;\n\n const colonIndex = trimmed.indexOf(\":\");\n if (colonIndex === -1) continue;\n\n const key = trimmed.slice(0, colonIndex).trim();\n let value = trimmed.slice(colonIndex + 1).trim();\n\n // Remove quotes if present\n if (\n (value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\"))\n ) {\n value = value.slice(1, -1);\n }\n\n result[key] = value;\n }\n\n return result;\n}\n\n/**\n * Load project-specific Trafic config from .ddev/config.trafic.yaml\n */\nexport function loadProjectConfig(projectDir: string): ProjectConfig {\n const configPath = join(projectDir, \".ddev\", \"config.trafic.yaml\");\n\n if (!existsSync(configPath)) {\n return {};\n }\n\n try {\n const content = readFileSync(configPath, \"utf-8\");\n const parsed = parseSimpleYaml(content);\n\n const config: ProjectConfig = {};\n\n // Validate auth_policy\n if (parsed.auth_policy) {\n const policy = parsed.auth_policy.toLowerCase();\n if ([\"allow\", \"deny\", \"basic\", \"token\"].includes(policy)) {\n config.auth_policy = policy as ProjectConfig[\"auth_policy\"];\n }\n }\n\n // Validate idle_timeout\n if (parsed.idle_timeout) {\n config.idle_timeout = parsed.idle_timeout;\n }\n\n return config;\n } catch (error) {\n console.warn(`Failed to load project config from ${configPath}:`, error);\n return {};\n }\n}\n\n/**\n * Check if a project should never be stopped (idle_timeout = \"never\")\n */\nexport function shouldNeverStop(config: ProjectConfig): boolean {\n return config.idle_timeout?.toLowerCase() === \"never\";\n}\n\n/**\n * Get idle timeout in milliseconds, or undefined to use global default\n */\nexport function getIdleTimeoutMs(config: ProjectConfig): number | undefined {\n if (!config.idle_timeout) return undefined;\n if (shouldNeverStop(config)) return undefined;\n\n // Parse duration string (e.g., \"30m\", \"4h\", \"2h30m\")\n const regex = /(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?/;\n const match = config.idle_timeout.match(regex);\n if (!match) return undefined;\n\n const hours = parseInt(match[1] ?? \"0\", 10);\n const minutes = parseInt(match[2] ?? \"0\", 10);\n const seconds = parseInt(match[3] ?? \"0\", 10);\n\n const ms = (hours * 3600 + minutes * 60 + seconds) * 1000;\n return ms > 0 ? ms : undefined;\n}\n","import { createServer, type IncomingMessage, type ServerResponse } from \"node:http\";\nimport { readFileSync } from \"node:fs\";\nimport { resolve, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { AgentConfig, AuthConfig } from \"./types.js\";\nimport { checkAuth, resolveClientIp } from \"./utils/auth.js\";\nimport {\n loadProjectList,\n buildHostnameIndex,\n watchProjectList,\n startProject,\n getProjectInfo,\n} from \"./utils/ddev.js\";\nimport {\n initDb,\n updateProjectAccess,\n logAccess,\n setProjectStatus,\n getProject,\n} from \"./utils/db.js\";\nimport { loadProjectConfig } from \"./utils/project-config.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// State\nlet config: AgentConfig;\nlet projectList: Map<string, string>;\nlet hostnameIndex: Map<string, string>;\n// Cache of per-project configs (project name -> config)\nconst projectConfigs = new Map<string, ReturnType<typeof loadProjectConfig>>();\n\n/**\n * Load HTML template\n */\nfunction loadTemplate(name: string): string {\n const templatePath = resolve(__dirname, \"..\", \"templates\", `${name}.html`);\n try {\n return readFileSync(templatePath, \"utf-8\");\n } catch {\n return `<html><body><h1>${name}</h1></body></html>`;\n }\n}\n\n/**\n * Get effective auth config for a project (merges global + per-project)\n */\nfunction getEffectiveAuthConfig(projectName: string | undefined): AuthConfig {\n if (!projectName) return config.auth;\n\n const projectConfig = projectConfigs.get(projectName);\n if (!projectConfig?.auth_policy) return config.auth;\n\n // Override default policy with project-specific policy\n return {\n ...config.auth,\n defaultPolicy: projectConfig.auth_policy,\n };\n}\n\n/**\n * Handle forward auth requests from Traefik\n * Traefik sends the original request headers, we return 200 (allow) or 401 (deny)\n */\nfunction handleAuth(req: IncomingMessage, res: ServerResponse): void {\n const hostname = req.headers[\"x-forwarded-host\"] as string ?? \"\";\n // Keep the two apart: the socket peer cannot be forged, X-Forwarded-For\n // partly can. checkAuth decides which entry to trust.\n const socketIp = req.socket.remoteAddress ?? \"\";\n const forwardedFor = req.headers[\"x-forwarded-for\"] as string | undefined;\n const authorization = req.headers[\"authorization\"];\n const path = req.headers[\"x-forwarded-uri\"] as string ?? \"/\";\n\n // Find project from hostname\n const projectName = hostnameIndex.get(hostname);\n\n // Get effective auth config (global + per-project overrides)\n const authConfig = getEffectiveAuthConfig(projectName);\n\n const clientIp = resolveClientIp(\n socketIp,\n forwardedFor,\n authConfig.trustedProxyHops,\n );\n\n const result = checkAuth(\n {\n hostname,\n ip: socketIp,\n authorization,\n forwardedFor,\n },\n authConfig,\n );\n\n if (result.allowed) {\n // Log access and update last access time\n if (projectName) {\n updateProjectAccess(projectName);\n logAccess({\n project: projectName,\n timestamp: Date.now(),\n ip: clientIp,\n userAgent: req.headers[\"user-agent\"] ?? \"\",\n path,\n });\n }\n\n res.writeHead(200);\n res.end();\n } else {\n // Return 401 with WWW-Authenticate header for basic auth\n res.writeHead(401, {\n \"WWW-Authenticate\": 'Basic realm=\"Trafic\"',\n });\n res.end(\"Unauthorized\");\n }\n}\n\n/**\n * Handle errors middleware requests (502 from Traefik)\n * When a project is stopped, Traefik returns 502. We show a waiting page and start the project.\n */\nasync function handleErrors(\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n const hostname = req.headers[\"x-forwarded-host\"] as string ?? req.headers.host ?? \"\";\n const projectName = hostnameIndex.get(hostname);\n\n if (!projectName) {\n // Unknown project\n const template = loadTemplate(\"error\");\n res.writeHead(404, { \"Content-Type\": \"text/html\" });\n res.end(template.replace(\"{{message}}\", \"Project not found\"));\n return;\n }\n\n // Check if project is already starting\n const record = getProject(projectName);\n if (record?.status === \"starting\") {\n // Show waiting page\n const template = loadTemplate(\"wait\");\n res.writeHead(503, {\n \"Content-Type\": \"text/html\",\n \"Retry-After\": \"5\",\n });\n res.end(\n template\n .replace(/\\{\\{project\\}\\}/g, projectName)\n .replace(/\\{\\{hostname\\}\\}/g, hostname),\n );\n return;\n }\n\n // Mark as starting\n setProjectStatus(projectName, \"starting\");\n\n // Show waiting page immediately\n const template = loadTemplate(\"wait\");\n res.writeHead(503, {\n \"Content-Type\": \"text/html\",\n \"Retry-After\": \"5\",\n });\n res.end(\n template\n .replace(/\\{\\{project\\}\\}/g, projectName)\n .replace(/\\{\\{hostname\\}\\}/g, hostname),\n );\n\n // Start project in background\n setTimeout(() => {\n const success = startProject(projectName);\n setProjectStatus(projectName, success ? \"running\" : \"stopped\");\n }, 100);\n}\n\n/**\n * Handle status polling requests\n */\nfunction handleStatus(req: IncomingMessage, res: ServerResponse): void {\n const url = new URL(req.url ?? \"/\", `http://${req.headers.host}`);\n const projectName = url.searchParams.get(\"project\");\n\n if (!projectName) {\n res.writeHead(400, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ error: \"Missing project parameter\" }));\n return;\n }\n\n const info = getProjectInfo(projectName);\n const record = getProject(projectName);\n\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(\n JSON.stringify({\n name: projectName,\n status: info?.status ?? record?.status ?? \"unknown\",\n ready: info?.status === \"running\",\n }),\n );\n}\n\n/**\n * Request handler\n */\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n): Promise<void> {\n const url = req.url ?? \"/\";\n\n try {\n // Route requests\n if (url === \"/__auth__\" || url.startsWith(\"/__auth__/\")) {\n handleAuth(req, res);\n } else if (url === \"/__status__\" || url.startsWith(\"/__status__/\")) {\n handleStatus(req, res);\n } else if (url === \"/__health__\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\" });\n res.end(JSON.stringify({ status: \"ok\", version: \"__VERSION__\" }));\n } else {\n // Default: errors middleware\n await handleErrors(req, res);\n }\n } catch (error) {\n console.error(\"Request error:\", error);\n res.writeHead(500, { \"Content-Type\": \"text/plain\" });\n res.end(\"Internal Server Error\");\n }\n}\n\n/**\n * Reload project list and their configs\n */\nfunction reloadProjects(): void {\n projectList = loadProjectList(config.projectListPath);\n hostnameIndex = buildHostnameIndex(projectList, config.tld);\n\n // Load per-project configs\n projectConfigs.clear();\n for (const [name, projectDir] of projectList) {\n const projectConfig = loadProjectConfig(projectDir);\n if (projectConfig.auth_policy || projectConfig.idle_timeout) {\n projectConfigs.set(name, projectConfig);\n console.log(` ${name}: auth=${projectConfig.auth_policy ?? \"default\"}, idle=${projectConfig.idle_timeout ?? \"default\"}`);\n }\n }\n\n console.log(`Loaded ${projectList.size} projects (${projectConfigs.size} with custom config)`);\n}\n\n/**\n * Start the agent server\n */\nexport function startServer(agentConfig: AgentConfig): void {\n config = agentConfig;\n\n // Initialize database\n initDb(config.dbPath);\n\n // Load projects\n reloadProjects();\n\n // Watch for changes\n watchProjectList(config.projectListPath, () => {\n console.log(\"Project list changed, reloading...\");\n reloadProjects();\n });\n\n // Create HTTP server\n const server = createServer((req, res) => {\n handleRequest(req, res).catch((error) => {\n console.error(\"Unhandled error:\", error);\n if (!res.headersSent) {\n res.writeHead(500);\n res.end(\"Internal Server Error\");\n }\n });\n });\n\n server.listen(config.port, () => {\n console.log(`Trafic agent listening on port ${config.port}`);\n console.log(`TLD: ${config.tld}`);\n console.log(`Projects: ${projectList.size}`);\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,YAAY,CAAC;AA8O1D;;GAEG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CA+B1D"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,YAAY,CAAC;AAuP1D;;GAEG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,WAAW,GAAG,IAAI,CA+B1D"}
package/dist/server.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as startServer } from "./server-VlzPosuk.js";
1
+ import { t as startServer } from "./server-zVWzPuj2.js";
2
2
  export { startServer };
@@ -1,4 +1,6 @@
1
1
  import { type SetupIo } from "./io.js";
2
+ /** Proxies in front of the agent when --trusted-proxy-hops is not given */
3
+ export declare const DEFAULT_TRUSTED_PROXY_HOPS = 1;
2
4
  /**
3
5
  * Add the NodeSource apt repository and GPG key.
4
6
  * Idempotent: safe to call even if the repository is already configured.
@@ -22,7 +24,10 @@ export declare function installAgent(io?: SetupIo): void;
22
24
  /**
23
25
  * Create the agent configuration
24
26
  */
25
- export declare function createAgentConfig(tld: string, _email?: string, io?: SetupIo): void;
27
+ export declare function createAgentConfig(options: {
28
+ tld: string;
29
+ trustedProxyHops?: number;
30
+ }, io?: SetupIo): void;
26
31
  /**
27
32
  * Create systemd service for the agent
28
33
  */
@@ -1 +1 @@
1
- {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/setup/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAS/C;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAuB/D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAqBtD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAOvD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,MAAM,CAAC,EAAE,MAAM,EACf,EAAE,GAAE,OAAgB,GACnB,IAAI,CAiDN;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAkD/D"}
1
+ {"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/setup/agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAM/C,2EAA2E;AAC3E,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAK5C;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAuB/D;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAqBtD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAOvD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,EACnD,EAAE,GAAE,OAAgB,GACnB,IAAI,CAyDN;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAkD/D"}
@@ -11,7 +11,10 @@ export declare function resolveAllowedUsers(allowedUsers: string[], sudoUser: st
11
11
  /**
12
12
  * Configure SSH hardening
13
13
  */
14
- export declare function hardenSsh(allowedUsers?: string[], io?: SetupIo): void;
14
+ export declare function hardenSsh(options?: {
15
+ allowedUsers?: string[];
16
+ noRootSsh?: boolean;
17
+ }, io?: SetupIo): void;
15
18
  /**
16
19
  * Configure UFW firewall
17
20
  */
@@ -35,5 +38,8 @@ export declare function configureFilePermissions(io?: SetupIo): void;
35
38
  /**
36
39
  * Run all hardening steps
37
40
  */
38
- export declare function hardenServer(sshUsers?: string[], io?: SetupIo): void;
41
+ export declare function hardenServer(options?: {
42
+ sshUsers?: string[];
43
+ noRootSsh?: boolean;
44
+ }, io?: SetupIo): void;
39
45
  //# sourceMappingURL=hardening.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hardening.d.ts","sourceRoot":"","sources":["../../src/setup/hardening.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAG/C;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EAAE,EACtB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,EAAE,CAQV;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,YAAY,GAAE,MAAM,EAAa,EACjC,EAAE,GAAE,OAAgB,GACnB,IAAI,CAmDN;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAuB5D;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CA+CtE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAiC5D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAchE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAuBnE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,GAAE,MAAM,EAAa,EAC7B,EAAE,GAAE,OAAgB,GACnB,IAAI,CAON"}
1
+ {"version":3,"file":"hardening.d.ts","sourceRoot":"","sources":["../../src/setup/hardening.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAG/C;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,YAAY,EAAE,MAAM,EAAE,EACtB,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,MAAM,EAAE,CAQV;AAED;;GAEG;AACH,wBAAgB,SAAS,CACvB,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,EAC9D,EAAE,GAAE,OAAgB,GACnB,IAAI,CA8EN;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAuB5D;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CA+CtE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAiC5D;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAchE;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,EAAE,GAAE,OAAgB,GAAG,IAAI,CAuBnE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAAO,EAC1D,EAAE,GAAE,OAAgB,GACnB,IAAI,CAUN"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/setup/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAU/C,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAE3B;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAoFhE;AAED;;GAEG;AACH,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ3C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/setup/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAU/C,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAE3B;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA4FhE;AAED;;GAEG;AACH,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAQ3C"}
@@ -39,6 +39,13 @@ export interface SetupOptions {
39
39
  noDdev?: boolean;
40
40
  /** Additional SSH users to allow */
41
41
  sshUsers?: string[];
42
+ /** Disable root SSH login entirely; recovery is via the provider's rescue mode */
43
+ noRootSsh?: boolean;
44
+ /**
45
+ * Proxies in front of the agent, written to the generated config.
46
+ * Only applies to a fresh install — an existing config is never rewritten.
47
+ */
48
+ trustedProxyHops?: number;
42
49
  /** Run in dry-run mode (show what would be done) */
43
50
  dryRun?: boolean;
44
51
  }
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/setup/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,GAAG,IAAI,IAAI,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,oDAAoD;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/setup/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,mEAAmE;IACnE,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,GAAG,IAAI,IAAI,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,SAAS,GAAG,SAAS,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2BAA2B;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oDAAoD;IACpD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd"}