feat: add L4 (TCP/UDP) proxy host support via caddy-l4

- New l4_proxy_hosts table and Drizzle migration (0015)
- Full CRUD model layer with validation, audit logging, and Caddy config
  generation (buildL4Servers integrating into buildCaddyDocument)
- Server actions, paginated list page, create/edit/delete dialogs
- L4 port manager sidecar (docker/l4-port-manager) that auto-recreates
  the caddy container when port mappings change via a trigger file
- Auto-detects Docker Compose project name from caddy container labels
- Supports both named-volume and bind-mount (COMPOSE_HOST_DIR) deployments
- getL4PortsStatus simplified: status file is sole source of truth,
  trigger files deleted after processing to prevent stuck 'Waiting' banner
- Navigation entry added (CableIcon)
- Tests: unit (entrypoint.sh invariants + validation), integration (ports
  lifecycle + caddy config), E2E (CRUD + functional routing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
fuomag9
2026-03-22 00:11:16 +01:00
parent fc680d4171
commit 3a4a4d51cf
26 changed files with 4766 additions and 3 deletions
+39
View File
@@ -0,0 +1,39 @@
import { NextRequest, NextResponse } from "next/server";
import { requireAdmin, checkSameOrigin } from "@/src/lib/auth";
import { getL4PortsDiff, getL4PortsStatus, applyL4Ports } from "@/src/lib/l4-ports";
/**
* GET /api/l4-ports — returns current port diff and apply status.
*/
export async function GET() {
try {
await requireAdmin();
const [diff, status] = await Promise.all([
getL4PortsDiff(),
getL4PortsStatus(),
]);
return NextResponse.json({ diff, status });
} catch {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
}
/**
* POST /api/l4-ports — trigger port apply (write override + trigger file).
*/
export async function POST(request: NextRequest) {
const originCheck = checkSameOrigin(request);
if (originCheck) return originCheck;
try {
await requireAdmin();
const status = await applyL4Ports();
return NextResponse.json({ status });
} catch (error) {
console.error("Failed to apply L4 ports:", error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : "Failed to apply L4 ports" },
{ status: 500 }
);
}
}