feat: Refactor CaddyManager and OneboxReverseProxy to use Docker service for Caddy management
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
/**
|
||||
* Reverse Proxy for Onebox
|
||||
*
|
||||
* Delegates to Caddy for production-grade reverse proxy with native SNI support,
|
||||
* HTTP/2, WebSocket proxying, and zero-downtime configuration updates.
|
||||
* Delegates to Caddy (running as Docker service) for production-grade reverse proxy
|
||||
* with native SNI support, HTTP/2, WebSocket proxying, and zero-downtime configuration updates.
|
||||
*
|
||||
* Routes use Docker service names (e.g., onebox-hello-world:80) for container-to-container
|
||||
* communication within the Docker overlay network.
|
||||
*/
|
||||
|
||||
import { logger } from '../logging.ts';
|
||||
@@ -15,7 +18,7 @@ interface IProxyRoute {
|
||||
targetHost: string;
|
||||
targetPort: number;
|
||||
serviceId: number;
|
||||
containerID?: string;
|
||||
serviceName?: string;
|
||||
}
|
||||
|
||||
export class OneboxReverseProxy {
|
||||
@@ -36,16 +39,10 @@ export class OneboxReverseProxy {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize reverse proxy - ensures Caddy binary is available
|
||||
* Initialize reverse proxy - Caddy runs as Docker service, no setup needed
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
try {
|
||||
await this.caddy.ensureBinary();
|
||||
logger.info('Reverse proxy initialized (Caddy)');
|
||||
} catch (error) {
|
||||
logger.error(`Failed to initialize reverse proxy: ${getErrorMessage(error)}`);
|
||||
throw error;
|
||||
}
|
||||
logger.info('Reverse proxy initialized (Caddy Docker service)');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -61,7 +58,7 @@ export class OneboxReverseProxy {
|
||||
try {
|
||||
// Start Caddy (handles both HTTP and HTTPS)
|
||||
await this.caddy.start();
|
||||
logger.success(`Reverse proxy started on port ${this.httpPort} (Caddy)`);
|
||||
logger.success(`Reverse proxy started on port ${this.httpPort} (Caddy Docker service)`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to start reverse proxy: ${getErrorMessage(error)}`);
|
||||
throw error;
|
||||
@@ -97,46 +94,32 @@ export class OneboxReverseProxy {
|
||||
|
||||
/**
|
||||
* Add a route for a service
|
||||
* Uses Docker service name for upstream (Caddy runs in same Docker network)
|
||||
*/
|
||||
async addRoute(serviceId: number, domain: string, targetPort: number): Promise<void> {
|
||||
try {
|
||||
// Get container IP from Docker
|
||||
// Get service info from database
|
||||
const service = this.database.getServiceByID(serviceId);
|
||||
if (!service || !service.containerID) {
|
||||
throw new Error(`Service not found or has no container: ${serviceId}`);
|
||||
if (!service) {
|
||||
throw new Error(`Service not found: ${serviceId}`);
|
||||
}
|
||||
|
||||
// Get container IP from Docker network
|
||||
let targetHost = 'localhost';
|
||||
try {
|
||||
const containerIP = await this.oneboxRef.docker.getContainerIP(service.containerID);
|
||||
if (containerIP) {
|
||||
targetHost = containerIP;
|
||||
} else {
|
||||
// Caddy runs on host, so we need the actual IP
|
||||
// Try getting task IP from Swarm
|
||||
const taskIP = await this.getSwarmTaskIP(service.containerID);
|
||||
if (taskIP) {
|
||||
targetHost = taskIP;
|
||||
} else {
|
||||
logger.warn(`Could not resolve IP for ${service.name}, using localhost`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`Could not resolve container IP for ${service.name}: ${getErrorMessage(error)}`);
|
||||
}
|
||||
// Use Docker service name as upstream target
|
||||
// Caddy runs on the same Docker network, so it can resolve service names directly
|
||||
const serviceName = `onebox-${service.name}`;
|
||||
const targetHost = serviceName;
|
||||
|
||||
const route: IProxyRoute = {
|
||||
domain,
|
||||
targetHost,
|
||||
targetPort,
|
||||
serviceId,
|
||||
containerID: service.containerID,
|
||||
serviceName,
|
||||
};
|
||||
|
||||
this.routes.set(domain, route);
|
||||
|
||||
// Add route to Caddy
|
||||
// Add route to Caddy using Docker service name
|
||||
const upstream = `${targetHost}:${targetPort}`;
|
||||
await this.caddy.addRoute(domain, upstream);
|
||||
|
||||
@@ -147,36 +130,6 @@ export class OneboxReverseProxy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get IP address for a Swarm task
|
||||
*/
|
||||
private async getSwarmTaskIP(containerIdOrTaskId: string): Promise<string | null> {
|
||||
try {
|
||||
// Try to get task details from Swarm
|
||||
const docker = this.oneboxRef.docker;
|
||||
|
||||
// First, try to find the task by inspecting the container
|
||||
const containerInfo = await docker.inspectContainer(containerIdOrTaskId);
|
||||
if (containerInfo?.NetworkSettings?.Networks) {
|
||||
// Get IP from the overlay network
|
||||
for (const [networkName, networkInfo] of Object.entries(containerInfo.NetworkSettings.Networks)) {
|
||||
if (networkName.includes('onebox') && (networkInfo as any).IPAddress) {
|
||||
return (networkInfo as any).IPAddress;
|
||||
}
|
||||
}
|
||||
// Fall back to any network
|
||||
for (const networkInfo of Object.values(containerInfo.NetworkSettings.Networks)) {
|
||||
if ((networkInfo as any).IPAddress) {
|
||||
return (networkInfo as any).IPAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a route
|
||||
*/
|
||||
@@ -213,6 +166,7 @@ export class OneboxReverseProxy {
|
||||
const services = this.database.getAllServices();
|
||||
|
||||
for (const service of services) {
|
||||
// Route by domain if running (containerID is the service ID for Swarm services)
|
||||
if (service.domain && service.status === 'running' && service.containerID) {
|
||||
await this.addRoute(service.id!, service.domain, service.port);
|
||||
}
|
||||
@@ -227,7 +181,7 @@ export class OneboxReverseProxy {
|
||||
|
||||
/**
|
||||
* Add TLS certificate for a domain
|
||||
* Writes PEM files to disk for Caddy to load
|
||||
* Sends PEM content to Caddy via Admin API
|
||||
*/
|
||||
async addCertificate(domain: string, certPem: string, keyPem: string): Promise<void> {
|
||||
if (!certPem || !keyPem) {
|
||||
@@ -288,7 +242,7 @@ export class OneboxReverseProxy {
|
||||
certificates: caddyStatus.certificates,
|
||||
},
|
||||
routes: caddyStatus.routes,
|
||||
backend: 'caddy',
|
||||
backend: 'caddy-docker',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user