feat: replace onebox ingress with SmartProxy

This commit is contained in:
2026-04-28 21:30:48 +00:00
parent 0f5ce708d9
commit c5d9158078
20 changed files with 697 additions and 824 deletions
+15 -16
View File
@@ -44,42 +44,42 @@ ts/database/
- All methods delegate to the appropriate repository - All methods delegate to the appropriate repository
- No breaking changes for existing code - No breaking changes for existing code
## Current Migration Version: 8 ## Current Migration Version: 15
Migration 8 converted certificate storage from file paths to PEM content. Migration 15 renames the core reverse proxy platform service from `caddy` to `smartproxy`.
## Reverse Proxy (November 2025 - Caddy Docker Service) ## Reverse Proxy (April 2026 - SmartProxy Docker Service)
The reverse proxy uses **Caddy** running as a Docker Swarm service for production-grade reverse proxying with native SNI support, HTTP/2, HTTP/3, and WebSocket handling. The reverse proxy uses **SmartProxy** running as a Docker Swarm service for production-grade reverse proxying with TLS termination and WebSocket handling.
**Architecture:** **Architecture:**
- Caddy runs as Docker Swarm service (`onebox-caddy`) on the overlay network - SmartProxy runs as Docker Swarm service (`onebox-smartproxy`) on the overlay network
- No binary download required - uses `caddy:2-alpine` Docker image - No host binary download required - uses `code.foss.global/host.today/ht-docker-smartproxy:latest`
- Configuration pushed dynamically via Caddy Admin API (port 2019) - Routes are pushed dynamically via the SmartProxy admin API (host port 2019)
- Automatic HTTPS disabled - certificates managed externally via SmartACME - Automatic HTTPS disabled - certificates managed externally via SmartACME
- Zero-downtime configuration updates - Zero-downtime configuration updates
- Services reached by Docker service name (e.g., `onebox-hello-world:80`) - Services reached by Docker service name (e.g., `onebox-hello-world:80`)
**Key files:** **Key files:**
- `ts/classes/caddy.ts` - CaddyManager class for Docker service and Admin API - `ts/classes/smartproxy.ts` - SmartProxyManager class for Docker service and Admin API
- `ts/classes/reverseproxy.ts` - Delegates to CaddyManager - `ts/classes/reverseproxy.ts` - Delegates to SmartProxyManager
**Certificate workflow:** **Certificate workflow:**
1. `CertRequirementManager` creates requirements for domains 1. `CertRequirementManager` creates requirements for domains
2. Daemon processes requirements via `certmanager.ts` 2. Daemon processes requirements via `certmanager.ts`
3. Certificates stored in database (PEM content) 3. Certificates stored in database (PEM content)
4. `reverseProxy.addCertificate()` passes PEM content to Caddy via `load_pem` (inline in config) 4. `reverseProxy.addCertificate()` passes PEM content to SmartProxy route config
5. Caddy serves TLS with the loaded certificates (no volume mounts needed) 5. SmartProxy serves TLS with the loaded certificates (no volume mounts needed)
**Docker Service Configuration:** **Docker Service Configuration:**
- Service name: `onebox-caddy` - Service name: `onebox-smartproxy`
- Image: `caddy:2-alpine` - Image: `code.foss.global/host.today/ht-docker-smartproxy:latest`
- Network: `onebox-network` (overlay, attachable) - Network: `onebox-network` (overlay, attachable)
- Startup: Writes initial config with `admin.listen: 0.0.0.0:2019` for host access - Startup: SmartProxy daemon admin API listens on container port 3000, published on host port 2019
**Port Mapping:** **Port Mapping:**
@@ -89,5 +89,4 @@ The reverse proxy uses **Caddy** running as a Docker Swarm service for productio
**Log Receiver:** **Log Receiver:**
- Caddy sends access logs to `tcp/172.17.0.1:9999` (Docker bridge gateway) - `ProxyLogReceiver` remains the host-side access-log stream endpoint for proxy log integrations
- `CaddyLogReceiver` on host receives and processes logs
+8 -8
View File
@@ -1,8 +1,8 @@
# @serve.zone/onebox # @serve.zone/onebox
> 🚀 Self-hosted Docker Swarm platform with Caddy reverse proxy, automatic SSL, and real-time WebSocket updates > 🚀 Self-hosted Docker Swarm platform with SmartProxy reverse proxy, automatic SSL, and real-time WebSocket updates
**Onebox** transforms any Linux server into a powerful container hosting platform. Deploy Docker Swarm services with automatic HTTPS, DNS configuration, and Caddy reverse proxy running as a Docker service - all managed through a modern web interface with real-time updates. **Onebox** transforms any Linux server into a powerful container hosting platform. Deploy Docker Swarm services with automatic HTTPS, DNS configuration, and SmartProxy reverse proxy running as a Docker service - all managed through a modern web interface with real-time updates.
## Issue Reporting and Security ## Issue Reporting and Security
@@ -10,12 +10,12 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
## What Makes Onebox Different? 🎯 ## What Makes Onebox Different? 🎯
- **Caddy Reverse Proxy in Docker** - Production-grade HTTP/HTTPS proxy running as a Swarm service with native service discovery, HTTP/2, HTTP/3, and bidirectional WebSocket proxying - **SmartProxy Reverse Proxy in Docker** - Production-grade HTTP/HTTPS proxy running as a Swarm service with native service discovery, TLS termination, and bidirectional WebSocket proxying
- **Docker Swarm First** - All workloads (including the reverse proxy!) run as Swarm services on the overlay network for seamless service-to-service communication - **Docker Swarm First** - All workloads (including the reverse proxy!) run as Swarm services on the overlay network for seamless service-to-service communication
- **Real-time Everything** - WebSocket-powered live updates for service status, logs, and metrics across all connected clients - **Real-time Everything** - WebSocket-powered live updates for service status, logs, and metrics across all connected clients
- **Single Executable** - Compiles to a standalone binary - just run it, no dependencies - **Single Executable** - Compiles to a standalone binary - just run it, no dependencies
- **Private Registry Included** - Built-in Docker registry with token-based auth and auto-deploy on push - **Private Registry Included** - Built-in Docker registry with token-based auth and auto-deploy on push
- **Zero Config SSL** - Automatic Let's Encrypt certificates with inline `load_pem` (no volume mounts needed) - **Zero Config SSL** - Automatic Let's Encrypt certificates passed directly into SmartProxy routes
- **Cloudflare Integration** - Automatic DNS record management and zone synchronization - **Cloudflare Integration** - Automatic DNS record management and zone synchronization
- **Modern Stack** - Deno runtime + SQLite database + typed web UI - **Modern Stack** - Deno runtime + SQLite database + typed web UI
@@ -24,7 +24,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
### Core Platform ### Core Platform
- 🐳 **Docker Swarm Management** - Deploy, scale, and orchestrate services with Swarm mode - 🐳 **Docker Swarm Management** - Deploy, scale, and orchestrate services with Swarm mode
- 🌐 **Caddy Reverse Proxy** - Production-grade proxy running as Docker service with SNI, HTTP/2, HTTP/3 - 🌐 **SmartProxy Reverse Proxy** - Production-grade proxy running as Docker service with TLS termination and WebSocket support
- 🔒 **Automatic SSL Certificates** - Let's Encrypt integration with hot-reload and renewal monitoring - 🔒 **Automatic SSL Certificates** - Let's Encrypt integration with hot-reload and renewal monitoring
- ☁️ **Cloudflare DNS Integration** - Automatic DNS record creation and zone synchronization - ☁️ **Cloudflare DNS Integration** - Automatic DNS record creation and zone synchronization
- 📦 **Built-in Registry** - Private Docker registry with per-service tokens and auto-update - 📦 **Built-in Registry** - Private Docker registry with per-service tokens and auto-update
@@ -117,7 +117,7 @@ Onebox is built with modern technologies for performance and developer experienc
│ ┌──────────────────────────────┐ │ │ ┌──────────────────────────────┐ │
│ │ onebox-network (overlay) │ │ │ │ onebox-network (overlay) │ │
│ ├──────────────────────────────┤ │ │ ├──────────────────────────────┤ │
│ │ onebox-caddy (Caddy proxy) │ │ │ │ onebox-smartproxy (proxy) │ │
│ │ HTTP (80) + HTTPS (443) │ │ │ │ HTTP (80) + HTTPS (443) │ │
│ │ Admin API → config updates │ │ │ │ Admin API → config updates │ │
│ ├──────────────────────────────┤ │ │ ├──────────────────────────────┤ │
@@ -137,7 +137,7 @@ Onebox is built with modern technologies for performance and developer experienc
| Component | Description | | Component | Description |
| ----------------------- | -------------------------------------------------------------------- | | ----------------------- | -------------------------------------------------------------------- |
| **Deno Runtime** | Modern TypeScript with built-in security | | **Deno Runtime** | Modern TypeScript with built-in security |
| **Caddy Reverse Proxy** | Docker Swarm service with HTTP/2, HTTP/3, SNI, and WebSocket support | | **SmartProxy Reverse Proxy** | Docker Swarm service with TLS termination and WebSocket support |
| **Docker Swarm** | Container orchestration (all workloads run as services) | | **Docker Swarm** | Container orchestration (all workloads run as services) |
| **SQLite Database** | Configuration, metrics, and user data | | **SQLite Database** | Configuration, metrics, and user data |
| **OpsServer** | TypedRequest API and TypedSocket real-time updates | | **OpsServer** | TypedRequest API and TypedSocket real-time updates |
@@ -321,7 +321,7 @@ onebox/
│ ├── classes/ # Core implementations │ ├── classes/ # Core implementations
│ │ ├── onebox.ts # Main coordinator │ │ ├── onebox.ts # Main coordinator
│ │ ├── reverseproxy.ts # Reverse proxy orchestration │ │ ├── reverseproxy.ts # Reverse proxy orchestration
│ │ ├── caddy.ts # Caddy Docker service management │ │ ├── smartproxy.ts # SmartProxy Docker service management
│ │ ├── docker.ts # Docker Swarm API │ │ ├── docker.ts # Docker Swarm API
│ │ ├── services.ts # Service orchestration │ │ ├── services.ts # Service orchestration
│ │ ├── certmanager.ts # SSL certificate management │ │ ├── certmanager.ts # SSL certificate management
-592
View File
@@ -1,592 +0,0 @@
/**
* Caddy Manager for Onebox
*
* Manages Caddy as a Docker Swarm service instead of a host binary.
* This allows Caddy to access services on the Docker overlay network.
*/
import * as plugins from '../plugins.ts';
import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts';
const CADDY_SERVICE_NAME = 'onebox-caddy';
const CADDY_IMAGE = 'caddy:2-alpine';
const DOCKER_GATEWAY_IP = '172.17.0.1'; // Docker bridge gateway for container-to-host communication
export interface ICaddyRoute {
domain: string;
upstream: string; // e.g., "onebox-hello-world:80"
}
export interface ICaddyCertificate {
domain: string;
certPem: string;
keyPem: string;
}
interface ICaddyLoggingConfig {
logs: {
[name: string]: {
writer: {
output: string;
address?: string;
dial_timeout?: string;
soft_start?: boolean;
};
encoder?: { format: string };
level?: string;
include?: string[];
};
};
}
interface ICaddyConfig {
admin: {
listen: string;
};
logging?: ICaddyLoggingConfig;
apps: {
http: {
servers: {
[key: string]: {
listen: string[];
routes: ICaddyRouteConfig[];
automatic_https?: {
disable?: boolean;
disable_redirects?: boolean;
};
logs?: {
default_logger_name: string;
};
};
};
};
tls?: {
automation?: {
policies: Array<{ issuers: never[] }>;
};
certificates?: {
load_pem?: Array<{
certificate: string;
key: string;
tags?: string[];
}>;
};
};
};
}
interface ICaddyRouteConfig {
match: Array<{ host: string[] }>;
handle: Array<{
handler: string;
upstreams?: Array<{ dial: string }>;
routes?: ICaddyRouteConfig[];
}>;
terminal?: boolean;
}
export class CaddyManager {
private dockerClient: InstanceType<typeof plugins.docker.Docker> | null = null;
private certsDir: string;
private adminUrl: string;
private httpPort: number;
private httpsPort: number;
private logReceiverPort: number;
private loggingEnabled: boolean;
private routes: Map<string, ICaddyRoute> = new Map();
private certificates: Map<string, ICaddyCertificate> = new Map();
private networkName = 'onebox-network';
private serviceRunning = false;
constructor(options?: {
certsDir?: string;
adminPort?: number;
httpPort?: number;
httpsPort?: number;
logReceiverPort?: number;
loggingEnabled?: boolean;
}) {
this.certsDir = options?.certsDir || './.nogit/certs';
this.adminUrl = `http://localhost:${options?.adminPort || 2019}`;
this.httpPort = options?.httpPort || 8080;
this.httpsPort = options?.httpsPort || 8443;
this.logReceiverPort = options?.logReceiverPort || 9999;
this.loggingEnabled = options?.loggingEnabled ?? true;
}
/**
* Initialize Docker client for Caddy service management
*/
private async ensureDockerClient(): Promise<void> {
if (!this.dockerClient) {
this.dockerClient = new plugins.docker.Docker({
socketPath: 'unix:///var/run/docker.sock',
});
await this.dockerClient.start();
}
}
/**
* Update listening ports (must call reloadConfig after if running)
*/
setPorts(httpPort: number, httpsPort: number): void {
this.httpPort = httpPort;
this.httpsPort = httpsPort;
}
/**
* Start Caddy as a Docker Swarm service
*/
async start(): Promise<void> {
if (this.serviceRunning) {
logger.warn('Caddy service is already running');
return;
}
try {
await this.ensureDockerClient();
// Create certs directory for backup/persistence
await Deno.mkdir(this.certsDir, { recursive: true });
logger.info('Starting Caddy Docker service...');
// Check if service already exists
const existingService = await this.getExistingService();
if (existingService) {
logger.info('Caddy service exists, removing old service...');
await this.removeService();
// Wait for service to be removed
await new Promise((resolve) => setTimeout(resolve, 2000));
}
// Get network ID
const networkId = await this.getNetworkId();
// Create Caddy Docker service
const response = await this.dockerClient!.request('POST', '/services/create', {
Name: CADDY_SERVICE_NAME,
Labels: {
'managed-by': 'onebox',
'onebox-type': 'caddy',
},
TaskTemplate: {
ContainerSpec: {
Image: CADDY_IMAGE,
// Start Caddy with admin listening on all interfaces so we can reach it from host
// Write minimal config to /tmp and start Caddy with that config
Command: ['sh', '-c', 'printf \'{"admin":{"listen":"0.0.0.0:2019"}}\' > /tmp/caddy.json && caddy run --config /tmp/caddy.json'],
},
Networks: [
{
Target: networkId,
},
],
RestartPolicy: {
Condition: 'any',
MaxAttempts: 0,
},
},
Mode: {
Replicated: {
Replicas: 1,
},
},
EndpointSpec: {
Ports: [
{
Protocol: 'tcp',
TargetPort: 80,
PublishedPort: this.httpPort,
PublishMode: 'host',
},
{
Protocol: 'tcp',
TargetPort: 443,
PublishedPort: this.httpsPort,
PublishMode: 'host',
},
{
Protocol: 'tcp',
TargetPort: 2019,
PublishedPort: 2019,
PublishMode: 'host',
},
],
},
});
if (response.statusCode >= 300) {
throw new Error(`Failed to create Caddy service: HTTP ${response.statusCode} - ${JSON.stringify(response.body)}`);
}
logger.info(`Caddy service created: ${response.body.ID}`);
// Wait for Admin API to be ready
await this.waitForReady();
this.serviceRunning = true;
// Now configure via Admin API with current routes and certificates
await this.reloadConfig();
logger.success(`Caddy started (HTTP: ${this.httpPort}, HTTPS: ${this.httpsPort}, Admin: ${this.adminUrl})`);
} catch (error) {
logger.error(`Failed to start Caddy: ${getErrorMessage(error)}`);
throw error;
}
}
/**
* Get existing Caddy service if any
*/
private async getExistingService(): Promise<any | null> {
try {
const response = await this.dockerClient!.request('GET', `/services/${CADDY_SERVICE_NAME}`, {});
if (response.statusCode === 200) {
return response.body;
}
return null;
} catch {
return null;
}
}
/**
* Remove the Caddy service
*/
private async removeService(): Promise<void> {
try {
await this.dockerClient!.request('DELETE', `/services/${CADDY_SERVICE_NAME}`, {});
} catch {
// Service may not exist
}
}
/**
* Get network ID by name
*/
private async getNetworkId(): Promise<string> {
const networks = await this.dockerClient!.listNetworks();
const network = networks.find((n: any) => n.Name === this.networkName);
if (!network) {
throw new Error(`Network not found: ${this.networkName}`);
}
return network.Id;
}
/**
* Wait for Caddy Admin API to be ready
*/
private async waitForReady(maxAttempts = 60, intervalMs = 500): Promise<void> {
for (let i = 0; i < maxAttempts; i++) {
try {
const response = await fetch(`${this.adminUrl}/config/`);
if (response.ok) {
return;
}
} catch {
// Not ready yet
}
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
throw new Error('Caddy service failed to start within timeout');
}
/**
* Stop Caddy Docker service
*/
async stop(): Promise<void> {
if (!this.serviceRunning && !(await this.getExistingService())) {
return;
}
try {
await this.ensureDockerClient();
logger.info('Stopping Caddy service...');
await this.removeService();
this.serviceRunning = false;
logger.info('Caddy service stopped');
} catch (error) {
logger.error(`Failed to stop Caddy: ${getErrorMessage(error)}`);
}
}
/**
* Check if Caddy Admin API is healthy
*/
async isHealthy(): Promise<boolean> {
try {
const response = await fetch(`${this.adminUrl}/config/`);
return response.ok;
} catch {
return false;
}
}
/**
* Check if Caddy service is running
*/
async isRunning(): Promise<boolean> {
try {
await this.ensureDockerClient();
const service = await this.getExistingService();
if (!service) return false;
// Check if service has running tasks
const tasksResponse = await this.dockerClient!.request(
'GET',
`/tasks?filters=${encodeURIComponent(JSON.stringify({ service: [CADDY_SERVICE_NAME] }))}`,
{}
);
if (tasksResponse.statusCode !== 200) return false;
const tasks = tasksResponse.body;
return tasks.some((task: any) => task.Status?.State === 'running');
} catch {
return false;
}
}
/**
* Build Caddy JSON configuration from current routes and certificates
*/
private buildConfig(): ICaddyConfig {
const routes: ICaddyRouteConfig[] = [];
// Add routes
for (const [domain, route] of this.routes) {
routes.push({
match: [{ host: [domain] }],
handle: [
{
handler: 'reverse_proxy',
upstreams: [{ dial: route.upstream }],
},
],
terminal: true,
});
}
// Build certificate load_pem entries (inline PEM content)
const loadPem: Array<{ certificate: string; key: string; tags?: string[] }> = [];
for (const [domain, cert] of this.certificates) {
loadPem.push({
certificate: cert.certPem,
key: cert.keyPem,
tags: [domain],
});
}
const config: ICaddyConfig = {
admin: {
listen: '0.0.0.0:2019', // Listen on all interfaces inside container
},
apps: {
http: {
servers: {
main: {
listen: [':80', ':443'],
routes,
// Disable automatic HTTPS to prevent Caddy from trying to obtain certs
automatic_https: {
disable: true,
},
},
},
},
},
};
// Add access logging configuration if enabled
if (this.loggingEnabled) {
config.logging = {
logs: {
access: {
writer: {
output: 'net',
// Use Docker bridge gateway IP to reach log receiver on host
address: `tcp/${DOCKER_GATEWAY_IP}:${this.logReceiverPort}`,
dial_timeout: '5s',
soft_start: true, // Continue even if log receiver is down
},
encoder: { format: 'json' },
level: 'INFO',
include: ['http.log.access'],
},
},
};
// Associate server with access logger
config.apps.http.servers.main.logs = {
default_logger_name: 'access',
};
}
// Add TLS config if we have certificates
if (loadPem.length > 0) {
config.apps.tls = {
automation: {
// Disable automatic HTTPS - we manage certs ourselves
policies: [{ issuers: [] }],
},
certificates: {
load_pem: loadPem,
},
};
}
return config;
}
/**
* Reload Caddy configuration via Admin API
*/
async reloadConfig(): Promise<void> {
const isRunning = await this.isRunning();
if (!isRunning) {
logger.warn('Caddy not running, cannot reload config');
return;
}
const config = this.buildConfig();
try {
const response = await fetch(`${this.adminUrl}/load`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(config),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`Failed to reload Caddy config: ${response.status} ${text}`);
}
logger.debug('Caddy configuration reloaded');
} catch (error) {
logger.error(`Failed to reload Caddy config: ${getErrorMessage(error)}`);
throw error;
}
}
/**
* Add or update a route
*/
async addRoute(domain: string, upstream: string): Promise<void> {
this.routes.set(domain, { domain, upstream });
if (await this.isRunning()) {
await this.reloadConfig();
}
logger.success(`Added Caddy route: ${domain} -> ${upstream}`);
}
/**
* Remove a route
*/
async removeRoute(domain: string): Promise<void> {
if (this.routes.delete(domain)) {
if (await this.isRunning()) {
await this.reloadConfig();
}
logger.success(`Removed Caddy route: ${domain}`);
}
}
/**
* Add or update a TLS certificate
* Stores PEM content in memory for Admin API, also writes to disk for backup
*/
async addCertificate(domain: string, certPem: string, keyPem: string): Promise<void> {
// Store PEM content in memory for buildConfig()
this.certificates.set(domain, {
domain,
certPem,
keyPem,
});
// Also write to disk for backup/persistence
try {
await Deno.mkdir(this.certsDir, { recursive: true });
await Deno.writeTextFile(`${this.certsDir}/${domain}.crt`, certPem);
await Deno.writeTextFile(`${this.certsDir}/${domain}.key`, keyPem);
} catch (error) {
logger.warn(`Failed to write certificate backup for ${domain}: ${getErrorMessage(error)}`);
}
if (await this.isRunning()) {
await this.reloadConfig();
}
logger.success(`Added TLS certificate for ${domain}`);
}
/**
* Remove a TLS certificate
*/
async removeCertificate(domain: string): Promise<void> {
if (this.certificates.delete(domain)) {
// Remove backup files
try {
await Deno.remove(`${this.certsDir}/${domain}.crt`);
await Deno.remove(`${this.certsDir}/${domain}.key`);
} catch {
// Files may not exist
}
if (await this.isRunning()) {
await this.reloadConfig();
}
logger.success(`Removed TLS certificate for ${domain}`);
}
}
/**
* Get all current routes
*/
getRoutes(): ICaddyRoute[] {
return Array.from(this.routes.values());
}
/**
* Get all current certificates
*/
getCertificates(): ICaddyCertificate[] {
return Array.from(this.certificates.values());
}
/**
* Clear all routes and certificates (useful for reload from database)
*/
clear(): void {
this.routes.clear();
this.certificates.clear();
}
/**
* Get status
*/
getStatus(): {
running: boolean;
httpPort: number;
httpsPort: number;
routes: number;
certificates: number;
} {
return {
running: this.serviceRunning,
httpPort: this.httpPort,
httpsPort: this.httpsPort,
routes: this.routes.size,
certificates: this.certificates.size,
};
}
}
+11 -11
View File
@@ -21,7 +21,7 @@ import { CertRequirementManager } from './cert-requirement-manager.ts';
import { RegistryManager } from './registry.ts'; import { RegistryManager } from './registry.ts';
import { PlatformServicesManager } from './platform-services/index.ts'; import { PlatformServicesManager } from './platform-services/index.ts';
import { AppStoreManager } from './appstore.ts'; import { AppStoreManager } from './appstore.ts';
import { CaddyLogReceiver } from './caddy-log-receiver.ts'; import { ProxyLogReceiver } from './proxy-log-receiver.ts';
import { BackupManager } from './backup-manager.ts'; import { BackupManager } from './backup-manager.ts';
import { BackupScheduler } from './backup-scheduler.ts'; import { BackupScheduler } from './backup-scheduler.ts';
import { OpsServer } from '../opsserver/index.ts'; import { OpsServer } from '../opsserver/index.ts';
@@ -41,7 +41,7 @@ export class Onebox {
public registry: RegistryManager; public registry: RegistryManager;
public platformServices: PlatformServicesManager; public platformServices: PlatformServicesManager;
public appStore: AppStoreManager; public appStore: AppStoreManager;
public caddyLogReceiver: CaddyLogReceiver; public proxyLogReceiver: ProxyLogReceiver;
public backupManager: BackupManager; public backupManager: BackupManager;
public backupScheduler: BackupScheduler; public backupScheduler: BackupScheduler;
public opsServer: OpsServer; public opsServer: OpsServer;
@@ -77,8 +77,8 @@ export class Onebox {
// Initialize App Store manager // Initialize App Store manager
this.appStore = new AppStoreManager(this); this.appStore = new AppStoreManager(this);
// Initialize Caddy log receiver // Initialize reverse proxy log receiver
this.caddyLogReceiver = new CaddyLogReceiver(9999); this.proxyLogReceiver = new ProxyLogReceiver(9999);
// Initialize Backup manager // Initialize Backup manager
this.backupManager = new BackupManager(this); this.backupManager = new BackupManager(this);
@@ -106,11 +106,11 @@ export class Onebox {
// Initialize Docker // Initialize Docker
await this.docker.init(); await this.docker.init();
// Start Caddy log receiver BEFORE reverse proxy (so Caddy can connect to it) // Start proxy log receiver before reverse proxy startup.
try { try {
await this.caddyLogReceiver.start(); await this.proxyLogReceiver.start();
} catch (error) { } catch (error) {
logger.warn(`Failed to start Caddy log receiver: ${getErrorMessage(error)}`); logger.warn(`Failed to start proxy log receiver: ${getErrorMessage(error)}`);
} }
// Initialize Reverse Proxy // Initialize Reverse Proxy
@@ -268,9 +268,9 @@ export class Onebox {
const providers = this.platformServices.getAllProviders(); const providers = this.platformServices.getAllProviders();
const platformServicesStatus = providers.map((provider) => { const platformServicesStatus = providers.map((provider) => {
const service = platformServices.find((s) => s.type === provider.type); const service = platformServices.find((s) => s.type === provider.type);
// For Caddy, check actual runtime status since it starts without a DB record // For SmartProxy, check actual runtime status since it starts without a DB record
let status = service?.status || 'not-deployed'; let status = service?.status || 'not-deployed';
if (provider.type === 'caddy') { if (provider.type === 'smartproxy') {
status = proxyStatus.http.running ? 'running' : 'stopped'; status = proxyStatus.http.running ? 'running' : 'stopped';
} }
// Count resources for this platform service // Count resources for this platform service
@@ -432,8 +432,8 @@ export class Onebox {
// Stop reverse proxy if running // Stop reverse proxy if running
await this.reverseProxy.stop(); await this.reverseProxy.stop();
// Stop Caddy log receiver // Stop proxy log receiver
await this.caddyLogReceiver.stop(); await this.proxyLogReceiver.stop();
// Close backup archive // Close backup archive
await this.backupManager.close(); await this.backupManager.close();
+2 -2
View File
@@ -14,7 +14,7 @@ import type {
import type { IPlatformServiceProvider } from './providers/base.ts'; import type { IPlatformServiceProvider } from './providers/base.ts';
import { MongoDBProvider } from './providers/mongodb.ts'; import { MongoDBProvider } from './providers/mongodb.ts';
import { MinioProvider } from './providers/minio.ts'; import { MinioProvider } from './providers/minio.ts';
import { CaddyProvider } from './providers/caddy.ts'; import { SmartProxyProvider } from './providers/smartproxy.ts';
import { ClickHouseProvider } from './providers/clickhouse.ts'; import { ClickHouseProvider } from './providers/clickhouse.ts';
import { MariaDBProvider } from './providers/mariadb.ts'; import { MariaDBProvider } from './providers/mariadb.ts';
import { RedisProvider } from './providers/redis.ts'; import { RedisProvider } from './providers/redis.ts';
@@ -41,7 +41,7 @@ export class PlatformServicesManager {
// Register providers // Register providers
this.registerProvider(new MongoDBProvider(this.oneboxRef)); this.registerProvider(new MongoDBProvider(this.oneboxRef));
this.registerProvider(new MinioProvider(this.oneboxRef)); this.registerProvider(new MinioProvider(this.oneboxRef));
this.registerProvider(new CaddyProvider(this.oneboxRef)); this.registerProvider(new SmartProxyProvider(this.oneboxRef));
this.registerProvider(new ClickHouseProvider(this.oneboxRef)); this.registerProvider(new ClickHouseProvider(this.oneboxRef));
this.registerProvider(new MariaDBProvider(this.oneboxRef)); this.registerProvider(new MariaDBProvider(this.oneboxRef));
this.registerProvider(new RedisProvider(this.oneboxRef)); this.registerProvider(new RedisProvider(this.oneboxRef));
@@ -1,110 +0,0 @@
/**
* Caddy Platform Service Provider
*
* Caddy is a core infrastructure service that provides reverse proxy functionality.
* Unlike other platform services:
* - It doesn't provision resources for user services
* - It's started automatically by Onebox and cannot be stopped by users
* - It delegates to the existing CaddyManager for actual operations
*/
import { BasePlatformServiceProvider } from './base.ts';
import type {
IService,
IPlatformResource,
IPlatformServiceConfig,
IProvisionedResource,
IEnvVarMapping,
TPlatformServiceType,
TPlatformResourceType,
} from '../../../types.ts';
import { logger } from '../../../logging.ts';
import type { Onebox } from '../../onebox.ts';
export class CaddyProvider extends BasePlatformServiceProvider {
readonly type: TPlatformServiceType = 'caddy';
readonly displayName = 'Caddy Reverse Proxy';
readonly resourceTypes: TPlatformResourceType[] = []; // Caddy doesn't provision resources
readonly isCore = true; // Core infrastructure - cannot be stopped by users
constructor(oneboxRef: Onebox) {
super(oneboxRef);
}
getDefaultConfig(): IPlatformServiceConfig {
return {
image: 'caddy:2-alpine',
port: 80,
volumes: [],
environment: {},
};
}
getEnvVarMappings(): IEnvVarMapping[] {
// Caddy doesn't inject any env vars into user services
return [];
}
/**
* Deploy Caddy container - delegates to CaddyManager via reverseProxy
*/
async deployContainer(): Promise<string> {
logger.info('Starting Caddy via reverse proxy manager...');
// Get the reverse proxy which manages Caddy
const reverseProxy = this.oneboxRef.reverseProxy;
// Start reverse proxy (which starts Caddy)
await reverseProxy.startHttp();
// Get Caddy status to find container ID
const status = reverseProxy.getStatus();
// Update platform service record
const platformService = this.oneboxRef.database.getPlatformServiceByType(this.type);
if (platformService) {
this.oneboxRef.database.updatePlatformService(platformService.id!, {
status: 'running',
containerId: 'onebox-caddy', // Service name for Swarm services
});
}
logger.success('Caddy platform service started');
return 'onebox-caddy';
}
/**
* Stop Caddy container - NOT ALLOWED for core infrastructure
*/
async stopContainer(_containerId: string): Promise<void> {
throw new Error('Caddy is a core infrastructure service and cannot be stopped');
}
/**
* Check if Caddy is healthy via the reverse proxy
*/
async healthCheck(): Promise<boolean> {
try {
const reverseProxy = this.oneboxRef.reverseProxy;
const status = reverseProxy.getStatus();
return status.http.running;
} catch (error) {
logger.debug(`Caddy health check failed: ${error}`);
return false;
}
}
/**
* Caddy doesn't provision resources for user services
*/
async provisionResource(_userService: IService): Promise<IProvisionedResource> {
throw new Error('Caddy does not provision resources for user services');
}
/**
* Caddy doesn't deprovision resources
*/
async deprovisionResource(_resource: IPlatformResource, _credentials: Record<string, string>): Promise<void> {
throw new Error('Caddy does not manage resources for user services');
}
}
@@ -0,0 +1,87 @@
/**
* SmartProxy Platform Service Provider
*
* SmartProxy is a core infrastructure service that provides reverse proxy functionality.
* Unlike other platform services:
* - It doesn't provision resources for user services
* - It's started automatically by Onebox and cannot be stopped by users
* - It delegates to the existing reverse proxy manager for actual operations
*/
import { BasePlatformServiceProvider } from './base.ts';
import type {
IService,
IPlatformResource,
IPlatformServiceConfig,
IProvisionedResource,
IEnvVarMapping,
TPlatformServiceType,
TPlatformResourceType,
} from '../../../types.ts';
import { logger } from '../../../logging.ts';
import type { Onebox } from '../../onebox.ts';
export class SmartProxyProvider extends BasePlatformServiceProvider {
readonly type: TPlatformServiceType = 'smartproxy';
readonly displayName = 'SmartProxy Reverse Proxy';
readonly resourceTypes: TPlatformResourceType[] = [];
readonly isCore = true;
constructor(oneboxRef: Onebox) {
super(oneboxRef);
}
getDefaultConfig(): IPlatformServiceConfig {
return {
image: 'code.foss.global/host.today/ht-docker-smartproxy:latest',
port: 80,
volumes: [],
environment: {},
};
}
getEnvVarMappings(): IEnvVarMapping[] {
return [];
}
async deployContainer(): Promise<string> {
logger.info('Starting SmartProxy via reverse proxy manager...');
const reverseProxy = this.oneboxRef.reverseProxy;
await reverseProxy.startHttp();
const platformService = this.oneboxRef.database.getPlatformServiceByType(this.type);
if (platformService) {
this.oneboxRef.database.updatePlatformService(platformService.id!, {
status: 'running',
containerId: 'onebox-smartproxy',
});
}
logger.success('SmartProxy platform service started');
return 'onebox-smartproxy';
}
async stopContainer(_containerId: string): Promise<void> {
throw new Error('SmartProxy is a core infrastructure service and cannot be stopped');
}
async healthCheck(): Promise<boolean> {
try {
const reverseProxy = this.oneboxRef.reverseProxy;
const status = reverseProxy.getStatus();
return status.http.running;
} catch (error) {
logger.debug(`SmartProxy health check failed: ${error}`);
return false;
}
}
async provisionResource(_userService: IService): Promise<IProvisionedResource> {
throw new Error('SmartProxy does not provision resources for user services');
}
async deprovisionResource(_resource: IPlatformResource, _credentials: Record<string, string>): Promise<void> {
throw new Error('SmartProxy does not manage resources for user services');
}
}
@@ -1,7 +1,7 @@
/** /**
* Caddy Log Receiver for Onebox * Proxy Log Receiver for Onebox
* *
* TCP server that receives access logs from Caddy and broadcasts them to WebSocket clients. * TCP server that receives reverse proxy access logs and broadcasts them to WebSocket clients.
* Supports per-client filtering by domain and adaptive sampling at high volume. * Supports per-client filtering by domain and adaptive sampling at high volume.
*/ */
@@ -18,9 +18,9 @@ export interface ILogFilter {
} }
/** /**
* Caddy access log entry structure (from Caddy JSON format) * Reverse proxy access log entry structure.
*/ */
export interface ICaddyAccessLog { export interface IProxyAccessLog {
ts: number; ts: number;
level?: string; level?: string;
logger?: string; logger?: string;
@@ -60,9 +60,9 @@ interface ILogClient {
} }
/** /**
* CaddyLogReceiver - TCP server for Caddy access logs * ProxyLogReceiver - TCP server for reverse proxy access logs
*/ */
export class CaddyLogReceiver { export class ProxyLogReceiver {
private server: Deno.TcpListener | null = null; private server: Deno.TcpListener | null = null;
private clients: Map<string, ILogClient> = new Map(); private clients: Map<string, ILogClient> = new Map();
private port: number; private port: number;
@@ -76,7 +76,7 @@ export class CaddyLogReceiver {
private logCounter = 0; private logCounter = 0;
// Ring buffer for recent logs (for late-joining clients) // Ring buffer for recent logs (for late-joining clients)
private recentLogs: ICaddyAccessLog[] = []; private recentLogs: IProxyAccessLog[] = [];
private maxRecentLogs = 100; private maxRecentLogs = 100;
// Traffic stats aggregation (hourly rolling window) // Traffic stats aggregation (hourly rolling window)
@@ -137,7 +137,7 @@ export class CaddyLogReceiver {
/** /**
* Record a request in traffic stats * Record a request in traffic stats
*/ */
private recordTrafficStats(log: ICaddyAccessLog): void { private recordTrafficStats(log: IProxyAccessLog): void {
const bucket = this.getCurrentStatsBucket(); const bucket = this.getCurrentStatsBucket();
bucket.requestCount++; bucket.requestCount++;
@@ -164,25 +164,25 @@ export class CaddyLogReceiver {
*/ */
async start(): Promise<void> { async start(): Promise<void> {
if (this.running) { if (this.running) {
logger.warn('CaddyLogReceiver is already running'); logger.warn('ProxyLogReceiver is already running');
return; return;
} }
try { try {
this.server = Deno.listen({ port: this.port, transport: 'tcp' }); this.server = Deno.listen({ port: this.port, transport: 'tcp' });
this.running = true; this.running = true;
logger.success(`CaddyLogReceiver started on TCP port ${this.port}`); logger.success(`ProxyLogReceiver started on TCP port ${this.port}`);
// Start accepting connections in background // Start accepting connections in background
this.acceptConnections(); this.acceptConnections();
} catch (error) { } catch (error) {
logger.error(`Failed to start CaddyLogReceiver: ${getErrorMessage(error)}`); logger.error(`Failed to start ProxyLogReceiver: ${getErrorMessage(error)}`);
throw error; throw error;
} }
} }
/** /**
* Accept incoming TCP connections from Caddy * Accept incoming TCP connections from the reverse proxy
*/ */
private async acceptConnections(): Promise<void> { private async acceptConnections(): Promise<void> {
if (!this.server) return; if (!this.server) return;
@@ -194,17 +194,17 @@ export class CaddyLogReceiver {
} }
} catch (error) { } catch (error) {
if (this.running) { if (this.running) {
logger.error(`CaddyLogReceiver accept error: ${getErrorMessage(error)}`); logger.error(`ProxyLogReceiver accept error: ${getErrorMessage(error)}`);
} }
} }
} }
/** /**
* Handle a single TCP connection from Caddy * Handle a single TCP connection from the reverse proxy
*/ */
private async handleConnection(conn: Deno.TcpConn): Promise<void> { private async handleConnection(conn: Deno.TcpConn): Promise<void> {
const remoteAddr = conn.remoteAddr as Deno.NetAddr; const remoteAddr = conn.remoteAddr as Deno.NetAddr;
logger.debug(`CaddyLogReceiver: Connection from ${remoteAddr.hostname}:${remoteAddr.port}`); logger.debug(`ProxyLogReceiver: Connection from ${remoteAddr.hostname}:${remoteAddr.port}`);
const reader = conn.readable.getReader(); const reader = conn.readable.getReader();
const decoder = new TextDecoder(); const decoder = new TextDecoder();
@@ -217,7 +217,7 @@ export class CaddyLogReceiver {
buffer += decoder.decode(value, { stream: true }); buffer += decoder.decode(value, { stream: true });
// Process complete lines (Caddy sends newline-delimited JSON) // Process complete newline-delimited JSON log lines.
const lines = buffer.split('\n'); const lines = buffer.split('\n');
buffer = lines.pop() || ''; // Keep incomplete line in buffer buffer = lines.pop() || ''; // Keep incomplete line in buffer
@@ -229,7 +229,7 @@ export class CaddyLogReceiver {
} }
} catch (error) { } catch (error) {
if (this.running) { if (this.running) {
logger.debug(`CaddyLogReceiver connection closed: ${getErrorMessage(error)}`); logger.debug(`ProxyLogReceiver connection closed: ${getErrorMessage(error)}`);
} }
} finally { } finally {
this.connections.delete(conn); this.connections.delete(conn);
@@ -242,18 +242,18 @@ export class CaddyLogReceiver {
} }
/** /**
* Process a single log line from Caddy * Process a single access log line
*/ */
private processLogLine(line: string): void { private processLogLine(line: string): void {
try { try {
const log = JSON.parse(line) as ICaddyAccessLog; const log = JSON.parse(line) as IProxyAccessLog;
// Only process access logs (check for http.log.access or just access, or any log with request/status) // Only process access logs (check for http.log.access or just access, or any log with request/status)
const isAccessLog = log.logger === 'http.log.access' || const isAccessLog = log.logger === 'http.log.access' ||
log.logger === 'access' || log.logger === 'access' ||
(log.request && typeof log.status === 'number'); (log.request && typeof log.status === 'number');
if (!isAccessLog) { if (!isAccessLog) {
logger.debug(`CaddyLogReceiver: Skipping non-access log: ${log.logger || 'unknown'}`); logger.debug(`ProxyLogReceiver: Skipping non-access log: ${log.logger || 'unknown'}`);
return; return;
} }
@@ -268,7 +268,7 @@ export class CaddyLogReceiver {
return; return;
} }
logger.debug(`CaddyLogReceiver: Access log received - ${log.request?.method} ${log.request?.host}${log.request?.uri} (status: ${log.status})`); logger.debug(`ProxyLogReceiver: Access log received - ${log.request?.method} ${log.request?.host}${log.request?.uri} (status: ${log.status})`);
// Add to recent logs buffer // Add to recent logs buffer
this.recentLogs.push(log); this.recentLogs.push(log);
@@ -277,10 +277,10 @@ export class CaddyLogReceiver {
} }
// Broadcast to WebSocket clients (log how many clients) // Broadcast to WebSocket clients (log how many clients)
logger.debug(`CaddyLogReceiver: Broadcasting to ${this.clients.size} clients`); logger.debug(`ProxyLogReceiver: Broadcasting to ${this.clients.size} clients`);
this.broadcast(log); this.broadcast(log);
} catch (error) { } catch (error) {
logger.debug(`Failed to parse Caddy log line: ${getErrorMessage(error)}`); logger.debug(`Failed to parse proxy log line: ${getErrorMessage(error)}`);
} }
} }
@@ -317,7 +317,7 @@ export class CaddyLogReceiver {
/** /**
* Broadcast a log entry to all connected WebSocket clients * Broadcast a log entry to all connected WebSocket clients
*/ */
private broadcast(log: ICaddyAccessLog): void { private broadcast(log: IProxyAccessLog): void {
const message = JSON.stringify({ const message = JSON.stringify({
type: 'access_log', type: 'access_log',
data: { data: {
@@ -365,7 +365,7 @@ export class CaddyLogReceiver {
/** /**
* Check if a log entry matches a client's filter * Check if a log entry matches a client's filter
*/ */
private matchesFilter(log: ICaddyAccessLog, filter: ILogFilter): boolean { private matchesFilter(log: IProxyAccessLog, filter: ILogFilter): boolean {
// Domain filter // Domain filter
if (filter.domain) { if (filter.domain) {
const logHost = log.request.host.toLowerCase(); const logHost = log.request.host.toLowerCase();
@@ -385,7 +385,7 @@ export class CaddyLogReceiver {
*/ */
addClient(clientId: string, ws: WebSocket, filter: ILogFilter = {}): void { addClient(clientId: string, ws: WebSocket, filter: ILogFilter = {}): void {
this.clients.set(clientId, { id: clientId, ws, filter }); this.clients.set(clientId, { id: clientId, ws, filter });
logger.debug(`CaddyLogReceiver: Added client ${clientId} (${this.clients.size} total)`); logger.debug(`ProxyLogReceiver: Added client ${clientId} (${this.clients.size} total)`);
// Send recent logs to new client // Send recent logs to new client
for (const log of this.recentLogs) { for (const log of this.recentLogs) {
@@ -422,7 +422,7 @@ export class CaddyLogReceiver {
*/ */
removeClient(clientId: string): void { removeClient(clientId: string): void {
if (this.clients.delete(clientId)) { if (this.clients.delete(clientId)) {
logger.debug(`CaddyLogReceiver: Removed client ${clientId} (${this.clients.size} remaining)`); logger.debug(`ProxyLogReceiver: Removed client ${clientId} (${this.clients.size} remaining)`);
} }
} }
@@ -433,7 +433,7 @@ export class CaddyLogReceiver {
const client = this.clients.get(clientId); const client = this.clients.get(clientId);
if (client) { if (client) {
client.filter = filter; client.filter = filter;
logger.debug(`CaddyLogReceiver: Updated filter for client ${clientId}`); logger.debug(`ProxyLogReceiver: Updated filter for client ${clientId}`);
} }
} }
@@ -470,7 +470,7 @@ export class CaddyLogReceiver {
// Clear clients // Clear clients
this.clients.clear(); this.clients.clear();
logger.info('CaddyLogReceiver stopped'); logger.info('ProxyLogReceiver stopped');
} }
/** /**
+39 -42
View File
@@ -1,8 +1,8 @@
/** /**
* Reverse Proxy for Onebox * Reverse Proxy for Onebox
* *
* Delegates to Caddy (running as Docker service) for production-grade reverse proxy * Delegates to SmartProxy (running as Docker service) for production-grade reverse proxy
* with native SNI support, HTTP/2, WebSocket proxying, and zero-downtime configuration updates. * with TLS termination, WebSocket proxying, and zero-downtime configuration updates.
* *
* Routes use Docker service names (e.g., onebox-hello-world:80) for container-to-container * Routes use Docker service names (e.g., onebox-hello-world:80) for container-to-container
* communication within the Docker overlay network. * communication within the Docker overlay network.
@@ -11,7 +11,7 @@
import { logger } from '../logging.ts'; import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts'; import { getErrorMessage } from '../utils/error.ts';
import { OneboxDatabase } from './database.ts'; import { OneboxDatabase } from './database.ts';
import { CaddyManager } from './caddy.ts'; import { SmartProxyManager } from './smartproxy.ts';
interface IProxyRoute { interface IProxyRoute {
domain: string; domain: string;
@@ -24,7 +24,7 @@ interface IProxyRoute {
export class OneboxReverseProxy { export class OneboxReverseProxy {
private oneboxRef: any; private oneboxRef: any;
private database: OneboxDatabase; private database: OneboxDatabase;
private caddy: CaddyManager; private smartProxy: SmartProxyManager;
private routes: Map<string, IProxyRoute> = new Map(); private routes: Map<string, IProxyRoute> = new Map();
private httpPort = 8080; // Default to dev ports (will be overridden if production) private httpPort = 8080; // Default to dev ports (will be overridden if production)
private httpsPort = 8443; private httpsPort = 8443;
@@ -32,33 +32,32 @@ export class OneboxReverseProxy {
constructor(oneboxRef: any) { constructor(oneboxRef: any) {
this.oneboxRef = oneboxRef; this.oneboxRef = oneboxRef;
this.database = oneboxRef.database; this.database = oneboxRef.database;
this.caddy = new CaddyManager({ this.smartProxy = new SmartProxyManager({
httpPort: this.httpPort, httpPort: this.httpPort,
httpsPort: this.httpsPort, httpsPort: this.httpsPort,
}); });
} }
/** /**
* Initialize reverse proxy - Caddy runs as Docker service, no setup needed * Initialize reverse proxy - SmartProxy runs as Docker service, no setup needed
*/ */
async init(): Promise<void> { async init(): Promise<void> {
logger.info('Reverse proxy initialized (Caddy Docker service)'); logger.info('Reverse proxy initialized (SmartProxy Docker service)');
} }
/** /**
* Start the HTTP/HTTPS reverse proxy server * Start the HTTP/HTTPS reverse proxy server
* Caddy handles both HTTP and HTTPS on the configured ports * SmartProxy handles both HTTP and HTTPS on the configured ports
*/ */
async startHttp(port?: number): Promise<void> { async startHttp(port?: number): Promise<void> {
if (port) { if (port) {
this.httpPort = port; this.httpPort = port;
this.caddy.setPorts(this.httpPort, this.httpsPort); this.smartProxy.setPorts(this.httpPort, this.httpsPort);
} }
try { try {
// Start Caddy (handles both HTTP and HTTPS) await this.smartProxy.start();
await this.caddy.start(); logger.success(`Reverse proxy started on port ${this.httpPort} (SmartProxy Docker service)`);
logger.success(`Reverse proxy started on port ${this.httpPort} (Caddy Docker service)`);
} catch (error) { } catch (error) {
logger.error(`Failed to start reverse proxy: ${getErrorMessage(error)}`); logger.error(`Failed to start reverse proxy: ${getErrorMessage(error)}`);
throw error; throw error;
@@ -66,21 +65,19 @@ export class OneboxReverseProxy {
} }
/** /**
* Start HTTPS - Caddy already handles HTTPS when started * Start HTTPS - SmartProxy already handles HTTPS when started
* This method exists for interface compatibility * This method exists for interface compatibility
*/ */
async startHttps(port?: number): Promise<void> { async startHttps(port?: number): Promise<void> {
if (port) { if (port) {
this.httpsPort = port; this.httpsPort = port;
this.caddy.setPorts(this.httpPort, this.httpsPort); this.smartProxy.setPorts(this.httpPort, this.httpsPort);
} }
// Caddy handles both HTTP and HTTPS together const status = this.smartProxy.getStatus();
// If already running, just log and optionally reload with new port
const status = this.caddy.getStatus();
if (status.running) { if (status.running) {
logger.info(`HTTPS already running on port ${this.httpsPort} via Caddy`); logger.info(`HTTPS already running on port ${this.httpsPort} via SmartProxy`);
} else { } else {
await this.caddy.start(); await this.smartProxy.start();
} }
} }
@@ -88,13 +85,13 @@ export class OneboxReverseProxy {
* Stop the reverse proxy * Stop the reverse proxy
*/ */
async stop(): Promise<void> { async stop(): Promise<void> {
await this.caddy.stop(); await this.smartProxy.stop();
logger.info('Reverse proxy stopped'); logger.info('Reverse proxy stopped');
} }
/** /**
* Add a route for a service * Add a route for a service
* Uses Docker service name for upstream (Caddy runs in same Docker network) * Uses Docker service name for upstream (SmartProxy runs in same Docker network)
*/ */
async addRoute(serviceId: number, domain: string, targetPort: number): Promise<void> { async addRoute(serviceId: number, domain: string, targetPort: number): Promise<void> {
try { try {
@@ -105,7 +102,7 @@ export class OneboxReverseProxy {
} }
// Use Docker service name as upstream target // Use Docker service name as upstream target
// Caddy runs on the same Docker network, so it can resolve service names directly // SmartProxy runs on the same Docker network, so it can resolve service names directly
const serviceName = `onebox-${service.name}`; const serviceName = `onebox-${service.name}`;
const targetHost = serviceName; const targetHost = serviceName;
@@ -119,9 +116,9 @@ export class OneboxReverseProxy {
this.routes.set(domain, route); this.routes.set(domain, route);
// Add route to Caddy using Docker service name // Add route to SmartProxy using Docker service name
const upstream = `${targetHost}:${targetPort}`; const upstream = `${targetHost}:${targetPort}`;
await this.caddy.addRoute(domain, upstream); await this.smartProxy.addRoute(domain, upstream);
logger.success(`Added proxy route: ${domain} -> ${upstream}`); logger.success(`Added proxy route: ${domain} -> ${upstream}`);
} catch (error) { } catch (error) {
@@ -135,9 +132,9 @@ export class OneboxReverseProxy {
*/ */
removeRoute(domain: string): void { removeRoute(domain: string): void {
if (this.routes.delete(domain)) { if (this.routes.delete(domain)) {
// Remove from Caddy (async but we don't wait) // Remove from SmartProxy (async but we don't wait)
this.caddy.removeRoute(domain).catch((error) => { this.smartProxy.removeRoute(domain).catch((error) => {
logger.error(`Failed to remove Caddy route for ${domain}: ${getErrorMessage(error)}`); logger.error(`Failed to remove SmartProxy route for ${domain}: ${getErrorMessage(error)}`);
}); });
logger.success(`Removed proxy route: ${domain}`); logger.success(`Removed proxy route: ${domain}`);
} else { } else {
@@ -159,9 +156,9 @@ export class OneboxReverseProxy {
try { try {
logger.info('Reloading proxy routes...'); logger.info('Reloading proxy routes...');
// Clear local and Caddy routes // Clear local and SmartProxy routes
this.routes.clear(); this.routes.clear();
this.caddy.clear(); this.smartProxy.clear();
const services = this.database.getAllServices(); const services = this.database.getAllServices();
@@ -181,7 +178,7 @@ export class OneboxReverseProxy {
/** /**
* Add TLS certificate for a domain * Add TLS certificate for a domain
* Sends PEM content to Caddy via Admin API * Sends PEM content to SmartProxy via Admin API
*/ */
async addCertificate(domain: string, certPem: string, keyPem: string): Promise<void> { async addCertificate(domain: string, certPem: string, keyPem: string): Promise<void> {
if (!certPem || !keyPem) { if (!certPem || !keyPem) {
@@ -189,14 +186,14 @@ export class OneboxReverseProxy {
return; return;
} }
await this.caddy.addCertificate(domain, certPem, keyPem); await this.smartProxy.addCertificate(domain, certPem, keyPem);
} }
/** /**
* Remove TLS certificate for a domain * Remove TLS certificate for a domain
*/ */
removeCertificate(domain: string): void { removeCertificate(domain: string): void {
this.caddy.removeCertificate(domain).catch((error) => { this.smartProxy.removeCertificate(domain).catch((error) => {
logger.error(`Failed to remove certificate for ${domain}: ${getErrorMessage(error)}`); logger.error(`Failed to remove certificate for ${domain}: ${getErrorMessage(error)}`);
}); });
} }
@@ -213,13 +210,13 @@ export class OneboxReverseProxy {
for (const cert of certificates) { for (const cert of certificates) {
// Use fullchainPem for the cert (includes intermediates) and keyPem for the key // Use fullchainPem for the cert (includes intermediates) and keyPem for the key
if (cert.domain && cert.fullchainPem && cert.keyPem) { if (cert.domain && cert.fullchainPem && cert.keyPem) {
await this.caddy.addCertificate(cert.domain, cert.fullchainPem, cert.keyPem); await this.smartProxy.addCertificate(cert.domain, cert.fullchainPem, cert.keyPem);
} else { } else {
logger.warn(`Skipping certificate for ${cert.domain}: missing PEM content`); logger.warn(`Skipping certificate for ${cert.domain}: missing PEM content`);
} }
} }
logger.success(`Loaded ${this.caddy.getCertificates().length} TLS certificates`); logger.success(`Loaded ${this.smartProxy.getCertificates().length} TLS certificates`);
} catch (error) { } catch (error) {
logger.error(`Failed to reload certificates: ${getErrorMessage(error)}`); logger.error(`Failed to reload certificates: ${getErrorMessage(error)}`);
throw error; throw error;
@@ -230,19 +227,19 @@ export class OneboxReverseProxy {
* Get status of reverse proxy * Get status of reverse proxy
*/ */
getStatus() { getStatus() {
const caddyStatus = this.caddy.getStatus(); const smartProxyStatus = this.smartProxy.getStatus();
return { return {
http: { http: {
running: caddyStatus.running, running: smartProxyStatus.running,
port: caddyStatus.httpPort, port: smartProxyStatus.httpPort,
}, },
https: { https: {
running: caddyStatus.running, running: smartProxyStatus.running,
port: caddyStatus.httpsPort, port: smartProxyStatus.httpsPort,
certificates: caddyStatus.certificates, certificates: smartProxyStatus.certificates,
}, },
routes: caddyStatus.routes, routes: smartProxyStatus.routes,
backend: 'caddy-docker', backend: 'smartproxy-docker',
}; };
} }
} }
+459
View File
@@ -0,0 +1,459 @@
/**
* SmartProxy Manager for Onebox
*
* Manages SmartProxy as a Docker Swarm service so it can route to services on
* the Onebox overlay network.
*/
import * as plugins from '../plugins.ts';
import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts';
const SMARTPROXY_SERVICE_NAME = 'onebox-smartproxy';
const SMARTPROXY_IMAGE = 'code.foss.global/host.today/ht-docker-smartproxy:latest';
const SMARTPROXY_ADMIN_CONTAINER_PORT = 3000;
const SMARTPROXY_HTTP_CONTAINER_PORT = 80;
const SMARTPROXY_HTTPS_CONTAINER_PORT = 443;
export interface ISmartProxyRoute {
domain: string;
upstream: string;
}
export interface ISmartProxyCertificate {
domain: string;
certPem: string;
keyPem: string;
}
interface ISmartProxyRouteConfig {
name: string;
match: {
ports: number;
domains: string;
protocol?: 'http' | 'tcp' | 'udp' | 'quic' | 'http3';
};
action: {
type: 'forward';
targets: Array<{ host: string; port: number }>;
tls?: {
mode: 'terminate';
certificate: {
key: string;
cert: string;
};
};
websocket?: {
enabled: boolean;
};
};
priority?: number;
}
export class SmartProxyManager {
private dockerClient: InstanceType<typeof plugins.docker.Docker> | null = null;
private certsDir: string;
private adminUrl: string;
private adminPort: number;
private httpPort: number;
private httpsPort: number;
private routes: Map<string, ISmartProxyRoute> = new Map();
private certificates: Map<string, ISmartProxyCertificate> = new Map();
private networkName = 'onebox-network';
private serviceRunning = false;
constructor(options?: {
certsDir?: string;
adminPort?: number;
httpPort?: number;
httpsPort?: number;
}) {
this.certsDir = options?.certsDir || './.nogit/certs';
this.adminPort = options?.adminPort || 2019;
this.adminUrl = `http://localhost:${this.adminPort}`;
this.httpPort = options?.httpPort || 8080;
this.httpsPort = options?.httpsPort || 8443;
}
private async ensureDockerClient(): Promise<void> {
if (!this.dockerClient) {
this.dockerClient = new plugins.docker.Docker({
socketPath: 'unix:///var/run/docker.sock',
});
await this.dockerClient.start();
}
}
setPorts(httpPort: number, httpsPort: number): void {
this.httpPort = httpPort;
this.httpsPort = httpsPort;
}
async start(): Promise<void> {
if (this.serviceRunning) {
logger.warn('SmartProxy service is already running');
return;
}
try {
await this.ensureDockerClient();
await Deno.mkdir(this.certsDir, { recursive: true });
logger.info('Starting SmartProxy Docker service...');
const existingService = await this.getExistingService();
if (existingService) {
logger.info('SmartProxy service exists, removing old service...');
await this.removeService();
await new Promise((resolve) => setTimeout(resolve, 2000));
}
const networkId = await this.getNetworkId();
const response = await this.dockerClient!.request('POST', '/services/create', {
Name: SMARTPROXY_SERVICE_NAME,
Labels: {
'managed-by': 'onebox',
'onebox-type': 'smartproxy',
},
TaskTemplate: {
ContainerSpec: {
Image: SMARTPROXY_IMAGE,
Env: [
'SMARTPROXY_ADMIN_HOST=0.0.0.0',
`SMARTPROXY_ADMIN_PORT=${SMARTPROXY_ADMIN_CONTAINER_PORT}`,
],
},
Networks: [
{
Target: networkId,
},
],
RestartPolicy: {
Condition: 'any',
MaxAttempts: 0,
},
},
Mode: {
Replicated: {
Replicas: 1,
},
},
EndpointSpec: {
Ports: [
{
Protocol: 'tcp',
TargetPort: SMARTPROXY_HTTP_CONTAINER_PORT,
PublishedPort: this.httpPort,
PublishMode: 'host',
},
{
Protocol: 'tcp',
TargetPort: SMARTPROXY_HTTPS_CONTAINER_PORT,
PublishedPort: this.httpsPort,
PublishMode: 'host',
},
{
Protocol: 'tcp',
TargetPort: SMARTPROXY_ADMIN_CONTAINER_PORT,
PublishedPort: this.adminPort,
PublishMode: 'host',
},
],
},
});
if (response.statusCode >= 300) {
throw new Error(`Failed to create SmartProxy service: HTTP ${response.statusCode} - ${JSON.stringify(response.body)}`);
}
logger.info(`SmartProxy service created: ${response.body.ID}`);
await this.waitForReady();
this.serviceRunning = true;
await this.reloadConfig();
logger.success(`SmartProxy started (HTTP: ${this.httpPort}, HTTPS: ${this.httpsPort}, Admin: ${this.adminUrl})`);
} catch (error) {
logger.error(`Failed to start SmartProxy: ${getErrorMessage(error)}`);
throw error;
}
}
private async getExistingService(): Promise<any | null> {
try {
const response = await this.dockerClient!.request('GET', `/services/${SMARTPROXY_SERVICE_NAME}`, {});
if (response.statusCode === 200) {
return response.body;
}
return null;
} catch {
return null;
}
}
private async removeService(): Promise<void> {
try {
await this.dockerClient!.request('DELETE', `/services/${SMARTPROXY_SERVICE_NAME}`, {});
} catch {
// Service may not exist.
}
}
private async getNetworkId(): Promise<string> {
const networks = await this.dockerClient!.listNetworks();
const network = networks.find((n: any) => n.Name === this.networkName);
if (!network) {
throw new Error(`Network not found: ${this.networkName}`);
}
return network.Id;
}
private async waitForReady(maxAttempts = 120, intervalMs = 1000): Promise<void> {
for (let i = 0; i < maxAttempts; i++) {
try {
const response = await fetch(`${this.adminUrl}/ready`);
if (response.ok) {
return;
}
} catch {
// Not ready yet.
}
await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
throw new Error('SmartProxy service failed to start within timeout');
}
async stop(): Promise<void> {
if (!this.serviceRunning && !(await this.getExistingService())) {
return;
}
try {
await this.ensureDockerClient();
logger.info('Stopping SmartProxy service...');
await this.removeService();
this.serviceRunning = false;
logger.info('SmartProxy service stopped');
} catch (error) {
logger.error(`Failed to stop SmartProxy: ${getErrorMessage(error)}`);
}
}
async isHealthy(): Promise<boolean> {
try {
const response = await fetch(`${this.adminUrl}/health`);
return response.ok;
} catch {
return false;
}
}
async isRunning(): Promise<boolean> {
try {
await this.ensureDockerClient();
const service = await this.getExistingService();
if (!service) return false;
const tasksResponse = await this.dockerClient!.request(
'GET',
`/tasks?filters=${encodeURIComponent(JSON.stringify({ service: [SMARTPROXY_SERVICE_NAME] }))}`,
{},
);
if (tasksResponse.statusCode !== 200) return false;
const tasks = tasksResponse.body;
return tasks.some((task: any) => task.Status?.State === 'running');
} catch {
return false;
}
}
private routeName(prefixArg: string, domainArg: string): string {
return `${prefixArg}-${domainArg.replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-|-$/g, '')}`;
}
private parseUpstream(upstreamArg: string): { host: string; port: number } {
const separatorIndex = upstreamArg.lastIndexOf(':');
if (separatorIndex <= 0 || separatorIndex === upstreamArg.length - 1) {
throw new Error(`Invalid upstream target: ${upstreamArg}`);
}
const host = upstreamArg.slice(0, separatorIndex);
const port = Number(upstreamArg.slice(separatorIndex + 1));
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error(`Invalid upstream port in target: ${upstreamArg}`);
}
return { host, port };
}
private buildRoutes(): ISmartProxyRouteConfig[] {
const routeConfigs: ISmartProxyRouteConfig[] = [];
for (const [domain, route] of this.routes) {
const target = this.parseUpstream(route.upstream);
const baseAction = {
type: 'forward' as const,
targets: [target],
websocket: {
enabled: true,
},
};
routeConfigs.push({
name: this.routeName('http', domain),
match: {
ports: SMARTPROXY_HTTP_CONTAINER_PORT,
domains: domain,
protocol: 'http',
},
action: baseAction,
priority: 10,
});
const certificate = this.certificates.get(domain);
if (certificate) {
routeConfigs.push({
name: this.routeName('https', domain),
match: {
ports: SMARTPROXY_HTTPS_CONTAINER_PORT,
domains: domain,
protocol: 'http',
},
action: {
...baseAction,
tls: {
mode: 'terminate',
certificate: {
key: certificate.keyPem,
cert: certificate.certPem,
},
},
},
priority: 20,
});
}
}
return routeConfigs;
}
async reloadConfig(): Promise<void> {
const isRunning = await this.isRunning();
if (!isRunning) {
logger.warn('SmartProxy not running, cannot reload config');
return;
}
const routes = this.buildRoutes();
try {
const response = await fetch(`${this.adminUrl}/routes`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ routes }),
});
if (!response.ok) {
const text = await response.text();
throw new Error(`Failed to reload SmartProxy routes: ${response.status} ${text}`);
}
logger.debug('SmartProxy routes reloaded');
} catch (error) {
logger.error(`Failed to reload SmartProxy routes: ${getErrorMessage(error)}`);
throw error;
}
}
async addRoute(domain: string, upstream: string): Promise<void> {
this.routes.set(domain, { domain, upstream });
if (await this.isRunning()) {
await this.reloadConfig();
}
logger.success(`Added SmartProxy route: ${domain} -> ${upstream}`);
}
async removeRoute(domain: string): Promise<void> {
if (this.routes.delete(domain)) {
if (await this.isRunning()) {
await this.reloadConfig();
}
logger.success(`Removed SmartProxy route: ${domain}`);
}
}
async addCertificate(domain: string, certPem: string, keyPem: string): Promise<void> {
this.certificates.set(domain, {
domain,
certPem,
keyPem,
});
try {
await Deno.mkdir(this.certsDir, { recursive: true });
await Deno.writeTextFile(`${this.certsDir}/${domain}.crt`, certPem);
await Deno.writeTextFile(`${this.certsDir}/${domain}.key`, keyPem);
} catch (error) {
logger.warn(`Failed to write certificate backup for ${domain}: ${getErrorMessage(error)}`);
}
if (await this.isRunning()) {
await this.reloadConfig();
}
logger.success(`Added TLS certificate for ${domain}`);
}
async removeCertificate(domain: string): Promise<void> {
if (this.certificates.delete(domain)) {
try {
await Deno.remove(`${this.certsDir}/${domain}.crt`);
await Deno.remove(`${this.certsDir}/${domain}.key`);
} catch {
// Files may not exist.
}
if (await this.isRunning()) {
await this.reloadConfig();
}
logger.success(`Removed TLS certificate for ${domain}`);
}
}
getRoutes(): ISmartProxyRoute[] {
return Array.from(this.routes.values());
}
getCertificates(): ISmartProxyCertificate[] {
return Array.from(this.certificates.values());
}
clear(): void {
this.routes.clear();
this.certificates.clear();
}
getStatus(): {
running: boolean;
httpPort: number;
httpsPort: number;
routes: number;
certificates: number;
} {
return {
running: this.serviceRunning,
httpPort: this.httpPort,
httpsPort: this.httpsPort,
routes: this.routes.size,
certificates: this.certificates.size,
};
}
}
@@ -0,0 +1,31 @@
import { BaseMigration } from './base-migration.ts';
import type { TQueryFunction } from '../types.ts';
export class Migration015SmartProxyPlatformService extends BaseMigration {
readonly version = 15;
readonly description = 'Rename Caddy platform service to SmartProxy';
up(query: TQueryFunction): void {
query(
`UPDATE platform_services
SET name = 'onebox-smartproxy',
type = 'smartproxy',
container_id = CASE
WHEN container_id = 'onebox-caddy' THEN 'onebox-smartproxy'
ELSE container_id
END,
config = ?,
updated_at = ?
WHERE type = 'caddy'`,
[
JSON.stringify({
image: 'code.foss.global/host.today/ht-docker-smartproxy:latest',
port: 80,
volumes: [],
environment: {},
}),
Date.now(),
],
);
}
}
@@ -21,6 +21,7 @@ import { Migration011ScopeColumns } from './migration-011-scope-columns.ts';
import { Migration012GfsRetention } from './migration-012-gfs-retention.ts'; import { Migration012GfsRetention } from './migration-012-gfs-retention.ts';
import { Migration013AppTemplateVersion } from './migration-013-app-template-version.ts'; import { Migration013AppTemplateVersion } from './migration-013-app-template-version.ts';
import { Migration014ContainerArchive } from './migration-014-containerarchive.ts'; import { Migration014ContainerArchive } from './migration-014-containerarchive.ts';
import { Migration015SmartProxyPlatformService } from './migration-015-smartproxy-platform-service.ts';
import type { BaseMigration } from './base-migration.ts'; import type { BaseMigration } from './base-migration.ts';
export class MigrationRunner { export class MigrationRunner {
@@ -46,6 +47,7 @@ export class MigrationRunner {
new Migration012GfsRetention(), new Migration012GfsRetention(),
new Migration013AppTemplateVersion(), new Migration013AppTemplateVersion(),
new Migration014ContainerArchive(), new Migration014ContainerArchive(),
new Migration015SmartProxyPlatformService(),
].sort((a, b) => a.version - b.version); ].sort((a, b) => a.version - b.version);
} }
+3 -3
View File
@@ -166,20 +166,20 @@ export class LogsHandler {
const encoder = new TextEncoder(); const encoder = new TextEncoder();
const clientId = crypto.randomUUID(); const clientId = crypto.randomUUID();
// Create a mock WebSocket-like object for the CaddyLogReceiver // Create a mock WebSocket-like object for the proxy log receiver.
const mockSocket = { const mockSocket = {
readyState: 1, // WebSocket.OPEN readyState: 1, // WebSocket.OPEN
send: (data: string) => { send: (data: string) => {
try { try {
virtualStream.sendData(encoder.encode(data)); virtualStream.sendData(encoder.encode(data));
} catch { } catch {
this.opsServerRef.oneboxRef.caddyLogReceiver.removeClient(clientId); this.opsServerRef.oneboxRef.proxyLogReceiver.removeClient(clientId);
} }
}, },
}; };
const filter = dataArg.filter || {}; const filter = dataArg.filter || {};
this.opsServerRef.oneboxRef.caddyLogReceiver.addClient( this.opsServerRef.oneboxRef.proxyLogReceiver.addClient(
clientId, clientId,
mockSocket as any, mockSocket as any,
filter, filter,
+3 -3
View File
@@ -19,7 +19,7 @@ export class NetworkHandler {
redis: 6379, redis: 6379,
postgresql: 5432, postgresql: 5432,
rabbitmq: 5672, rabbitmq: 5672,
caddy: 80, smartproxy: 80,
clickhouse: 8123, clickhouse: 8123,
mariadb: 3306, mariadb: 3306,
}; };
@@ -85,7 +85,7 @@ export class NetworkHandler {
async (dataArg) => { async (dataArg) => {
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg); await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any; const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any;
const logReceiverStats = this.opsServerRef.oneboxRef.caddyLogReceiver.getStats(); const logReceiverStats = this.opsServerRef.oneboxRef.proxyLogReceiver.getStats();
return { return {
stats: { stats: {
@@ -115,7 +115,7 @@ export class NetworkHandler {
'getTrafficStats', 'getTrafficStats',
async (dataArg) => { async (dataArg) => {
await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg); await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
const trafficStats = this.opsServerRef.oneboxRef.caddyLogReceiver.getTrafficStats(60); const trafficStats = this.opsServerRef.oneboxRef.proxyLogReceiver.getTrafficStats(60);
return { stats: trafficStats }; return { stats: trafficStats };
}, },
), ),
+2 -2
View File
@@ -119,7 +119,7 @@ export class PlatformHandler {
const isCore = 'isCore' in provider && (provider as any).isCore === true; const isCore = 'isCore' in provider && (provider as any).isCore === true;
let status: string = service?.status || 'not-deployed'; let status: string = service?.status || 'not-deployed';
if (provider.type === 'caddy') { if (provider.type === 'smartproxy') {
const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any; const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any;
status = (proxyStatus.running ?? proxyStatus.http?.running) ? 'running' : 'stopped'; status = (proxyStatus.running ?? proxyStatus.http?.running) ? 'running' : 'stopped';
} }
@@ -156,7 +156,7 @@ export class PlatformHandler {
const isCore = 'isCore' in provider && (provider as any).isCore === true; const isCore = 'isCore' in provider && (provider as any).isCore === true;
let rawStatus: string = service?.status || 'not-deployed'; let rawStatus: string = service?.status || 'not-deployed';
if (dataArg.serviceType === 'caddy') { if (dataArg.serviceType === 'smartproxy') {
const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any; const proxyStatus = this.opsServerRef.oneboxRef.reverseProxy.getStatus() as any;
rawStatus = (proxyStatus.running ?? proxyStatus.http?.running) ? 'running' : 'stopped'; rawStatus = (proxyStatus.running ?? proxyStatus.http?.running) ? 'running' : 'stopped';
} }
+1 -1
View File
@@ -78,7 +78,7 @@ export interface ITokenCreatedResponse {
} }
// Platform service types // Platform service types
export type TPlatformServiceType = 'mongodb' | 'minio' | 'redis' | 'postgresql' | 'rabbitmq' | 'caddy' | 'clickhouse' | 'mariadb'; export type TPlatformServiceType = 'mongodb' | 'minio' | 'redis' | 'postgresql' | 'rabbitmq' | 'smartproxy' | 'clickhouse' | 'mariadb';
export type TPlatformResourceType = 'database' | 'bucket' | 'cache' | 'queue'; export type TPlatformResourceType = 'database' | 'bucket' | 'cache' | 'queue';
export type TPlatformServiceStatus = 'stopped' | 'starting' | 'running' | 'stopping' | 'failed'; export type TPlatformServiceStatus = 'stopped' | 'starting' | 'running' | 'stopping' | 'failed';
+1 -1
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -41,7 +41,7 @@ export interface ITrafficStats {
errorRate: number; errorRate: number;
} }
export interface ICaddyAccessLog { export interface IProxyAccessLog {
ts: number; ts: number;
request: { request: {
remote_ip: string; remote_ip: string;
@@ -59,6 +59,6 @@ export interface INetworkLogMessage {
type: 'connected' | 'access_log' | 'filter_updated'; type: 'connected' | 'access_log' | 'filter_updated';
clientId?: string; clientId?: string;
filter?: { domain?: string; sampleRate?: number }; filter?: { domain?: string; sampleRate?: number };
data?: ICaddyAccessLog; data?: IProxyAccessLog;
timestamp: number; timestamp: number;
} }
+1 -1
View File
@@ -2,7 +2,7 @@
* Platform service data shapes for Onebox * Platform service data shapes for Onebox
*/ */
export type TPlatformServiceType = 'mongodb' | 'minio' | 'redis' | 'postgresql' | 'rabbitmq' | 'caddy' | 'clickhouse' | 'mariadb'; export type TPlatformServiceType = 'mongodb' | 'minio' | 'redis' | 'postgresql' | 'rabbitmq' | 'smartproxy' | 'clickhouse' | 'mariadb';
export type TPlatformServiceStatus = 'not-deployed' | 'stopped' | 'starting' | 'running' | 'stopping' | 'failed'; export type TPlatformServiceStatus = 'not-deployed' | 'stopped' | 'starting' | 'running' | 'stopping' | 'failed';
export type TPlatformResourceType = 'database' | 'bucket' | 'cache' | 'queue'; export type TPlatformResourceType = 'database' | 'bucket' | 'cache' | 'queue';
+1 -1
View File
@@ -609,7 +609,7 @@ export class ObViewServices extends DeesElement {
mongodb: { host: 'onebox-mongodb', port: 27017, version: '4.4', config: { engine: 'WiredTiger', authEnabled: true } }, mongodb: { host: 'onebox-mongodb', port: 27017, version: '4.4', config: { engine: 'WiredTiger', authEnabled: true } },
minio: { host: 'onebox-minio', port: 9000, version: 'latest', config: { consolePort: 9001, region: 'us-east-1' } }, minio: { host: 'onebox-minio', port: 9000, version: 'latest', config: { consolePort: 9001, region: 'us-east-1' } },
clickhouse: { host: 'onebox-clickhouse', port: 8123, version: 'latest', config: { nativePort: 9000, httpPort: 8123 } }, clickhouse: { host: 'onebox-clickhouse', port: 8123, version: 'latest', config: { nativePort: 9000, httpPort: 8123 } },
caddy: { host: 'onebox-caddy', port: 80, version: '2-alpine', config: { httpsPort: 443, adminApi: 2019 } }, smartproxy: { host: 'onebox-smartproxy', port: 80, version: 'latest', config: { httpsPort: 443, adminApi: 2019 } },
mariadb: { host: 'onebox-mariadb', port: 3306, version: '11', config: { engine: 'InnoDB', authEnabled: true } }, mariadb: { host: 'onebox-mariadb', port: 3306, version: '11', config: { engine: 'InnoDB', authEnabled: true } },
redis: { host: 'onebox-redis', port: 6379, version: '7-alpine', config: { appendonly: true, maxDatabases: 16 } }, redis: { host: 'onebox-redis', port: 6379, version: '7-alpine', config: { appendonly: true, maxDatabases: 16 } },
}; };