This commit is contained in:
Philipp Kunz 2025-05-19 22:47:13 +00:00
parent 62dc067a2a
commit e61766959f
14 changed files with 25 additions and 1946 deletions

View File

@ -1,100 +0,0 @@
# ACME Certificate Provisioning Timing Fix (v19.3.9)
## Problem Description
In SmartProxy v19.3.8 and earlier, ACME certificate provisioning would start immediately during SmartProxy initialization, before the required ports were actually listening. This caused ACME HTTP-01 challenges to fail because the challenge port (typically port 80) was not ready to accept connections when Let's Encrypt tried to validate the challenge.
## Root Cause
The certificate manager was initialized and immediately started provisioning certificates as part of the SmartProxy startup sequence:
1. SmartProxy.start() called
2. Certificate manager initialized
3. Certificate provisioning started immediately (including ACME challenges)
4. Port listeners started afterwards
5. ACME challenges would fail because port 80 wasn't listening yet
This race condition meant that when Let's Encrypt tried to connect to port 80 to validate the HTTP-01 challenge, the connection would be refused.
## Solution
The fix defers certificate provisioning until after all ports are listening and ready:
### Changes to SmartCertManager
```typescript
// Modified initialize() to skip automatic provisioning
public async initialize(): Promise<void> {
// ... initialization code ...
// Skip automatic certificate provisioning during initialization
console.log('Certificate manager initialized. Deferring certificate provisioning until after ports are listening.');
// Start renewal timer
this.startRenewalTimer();
}
// Made provisionAllCertificates public to allow direct calling after ports are ready
public async provisionAllCertificates(): Promise<void> {
// ... certificate provisioning code ...
}
```
### Changes to SmartProxy
```typescript
public async start() {
// ... initialization code ...
// Start port listeners using the PortManager
await this.portManager.addPorts(listeningPorts);
// Now that ports are listening, provision any required certificates
if (this.certManager) {
console.log('Starting certificate provisioning now that ports are ready');
await this.certManager.provisionAllCertificates();
}
// ... rest of startup code ...
}
```
## Timing Sequence
### Before (v19.3.8 and earlier)
1. Initialize certificate manager
2. Start ACME provisioning immediately
3. ACME challenge fails (port not ready)
4. Start port listeners
5. Port 80 now listening (too late)
### After (v19.3.9)
1. Initialize certificate manager (provisioning deferred)
2. Start port listeners
3. Port 80 now listening
4. Start ACME provisioning
5. ACME challenge succeeds
## Configuration
No configuration changes are required. The timing fix is automatic and transparent to users.
## Testing
The fix is verified by the test in `test/test.acme-timing-simple.ts` which ensures:
1. Certificate manager is initialized first
2. Ports start listening
3. Certificate provisioning happens only after ports are ready
## Impact
This fix ensures that:
- ACME HTTP-01 challenges succeed on first attempt
- No more "connection refused" errors during certificate provisioning
- Certificate acquisition is more reliable
- No manual retries needed for failed challenges
## Migration
Simply update to SmartProxy v19.3.9 or later. The fix is backward compatible and requires no changes to existing code or configuration.

View File

@ -1,348 +0,0 @@
# Certificate Management in SmartProxy v19+
## Overview
SmartProxy v19+ enhances certificate management with support for both global and route-level ACME configuration. This guide covers the updated certificate management system, which now supports flexible configuration hierarchies.
## Key Changes from Previous Versions
### v19.0.0 Changes
- **Global ACME configuration**: Set default ACME settings for all routes with `certificate: 'auto'`
- **Configuration hierarchy**: Top-level ACME settings serve as defaults, route-level settings override
- **Better error messages**: Clear guidance when ACME configuration is missing
- **Improved validation**: Configuration validation warns about common issues
### v18.0.0 Changes (from v17)
- **No backward compatibility**: Clean break from the legacy certificate system
- **No separate Port80Handler**: ACME challenges handled as regular SmartProxy routes
- **Unified route-based configuration**: Certificates configured directly in route definitions
- **Direct integration with @push.rocks/smartacme**: Leverages SmartAcme's built-in capabilities
## Configuration
### Global ACME Configuration (New in v19+)
Set default ACME settings at the top level that apply to all routes with `certificate: 'auto'`:
```typescript
const proxy = new SmartProxy({
// Global ACME defaults
acme: {
email: 'ssl@example.com', // Required for Let's Encrypt
useProduction: false, // Use staging by default
port: 80, // Port for HTTP-01 challenges
renewThresholdDays: 30, // Renew 30 days before expiry
certificateStore: './certs', // Certificate storage directory
autoRenew: true, // Enable automatic renewal
renewCheckIntervalHours: 24 // Check for renewals daily
},
routes: [
// Routes using certificate: 'auto' will inherit global settings
{
name: 'website',
match: { ports: 443, domains: 'example.com' },
action: {
type: 'forward',
target: { host: 'localhost', port: 8080 },
tls: {
mode: 'terminate',
certificate: 'auto' // Uses global ACME configuration
}
}
}
]
});
```
### Route-Level Certificate Configuration
Certificates are now configured at the route level using the `tls` property:
```typescript
const route: IRouteConfig = {
name: 'secure-website',
match: {
ports: 443,
domains: ['example.com', 'www.example.com']
},
action: {
type: 'forward',
target: { host: 'localhost', port: 8080 },
tls: {
mode: 'terminate',
certificate: 'auto', // Use ACME (Let's Encrypt)
acme: {
email: 'admin@example.com',
useProduction: true,
renewBeforeDays: 30
}
}
}
};
```
### Static Certificate Configuration
For manually managed certificates:
```typescript
const route: IRouteConfig = {
name: 'api-endpoint',
match: {
ports: 443,
domains: 'api.example.com'
},
action: {
type: 'forward',
target: { host: 'localhost', port: 9000 },
tls: {
mode: 'terminate',
certificate: {
certFile: './certs/api.crt',
keyFile: './certs/api.key',
ca: '...' // Optional CA chain
}
}
}
};
```
## TLS Modes
SmartProxy supports three TLS modes:
1. **terminate**: Decrypt TLS at the proxy and forward plain HTTP
2. **passthrough**: Pass encrypted TLS traffic directly to the backend
3. **terminate-and-reencrypt**: Decrypt at proxy, then re-encrypt to backend
## Certificate Storage
Certificates are stored in the `./certs` directory by default:
```
./certs/
├── route-name/
│ ├── cert.pem
│ ├── key.pem
│ ├── ca.pem (if available)
│ └── meta.json
```
## ACME Integration
### How It Works
1. SmartProxy creates a high-priority route for ACME challenges
2. When ACME server makes requests to `/.well-known/acme-challenge/*`, SmartProxy handles them automatically
3. Certificates are obtained and stored locally
4. Automatic renewal checks every 12 hours
### Configuration Options
```typescript
export interface IRouteAcme {
email: string; // Contact email for ACME account
useProduction?: boolean; // Use production servers (default: false)
challengePort?: number; // Port for HTTP-01 challenges (default: 80)
renewBeforeDays?: number; // Days before expiry to renew (default: 30)
}
```
## Advanced Usage
### Manual Certificate Operations
```typescript
// Get certificate status
const status = proxy.getCertificateStatus('route-name');
console.log(status);
// {
// domain: 'example.com',
// status: 'valid',
// source: 'acme',
// expiryDate: Date,
// issueDate: Date
// }
// Force certificate renewal
await proxy.renewCertificate('route-name');
// Manually provision a certificate
await proxy.provisionCertificate('route-name');
```
### Events
SmartProxy emits certificate-related events:
```typescript
proxy.on('certificate:issued', (event) => {
console.log(`New certificate for ${event.domain}`);
});
proxy.on('certificate:renewed', (event) => {
console.log(`Certificate renewed for ${event.domain}`);
});
proxy.on('certificate:expiring', (event) => {
console.log(`Certificate expiring soon for ${event.domain}`);
});
```
## Migration from Previous Versions
### Before (v17 and earlier)
```typescript
// Old approach with Port80Handler
const smartproxy = new SmartProxy({
port: 443,
acme: {
enabled: true,
accountEmail: 'admin@example.com',
// ... other ACME options
}
});
// Certificate provisioning was automatic or via certProvisionFunction
```
### After (v19+)
```typescript
// New approach with global ACME configuration
const smartproxy = new SmartProxy({
// Global ACME defaults (v19+)
acme: {
email: 'ssl@bleu.de',
useProduction: true,
port: 80 // Or 8080 for non-privileged
},
routes: [{
match: { ports: 443, domains: 'example.com' },
action: {
type: 'forward',
target: { host: 'localhost', port: 8080 },
tls: {
mode: 'terminate',
certificate: 'auto' // Uses global ACME settings
}
}
}]
});
```
## Troubleshooting
### Common Issues
1. **Certificate not provisioning**: Ensure the ACME challenge port (80 or configured port) is accessible
2. **ACME rate limits**: Use staging environment for testing (`useProduction: false`)
3. **Permission errors**: Ensure the certificate directory is writable
4. **Invalid email domain**: ACME servers may reject certain email domains (e.g., example.com). Use a real email domain
5. **Port binding errors**: Use higher ports (e.g., 8080) if running without root privileges
### Using Non-Privileged Ports
For development or non-root environments:
```typescript
const proxy = new SmartProxy({
acme: {
email: 'ssl@bleu.de',
port: 8080, // Use 8080 instead of 80
useProduction: false
},
routes: [
{
match: { ports: 8443, domains: 'example.com' },
action: {
type: 'forward',
target: { host: 'localhost', port: 3000 },
tls: {
mode: 'terminate',
certificate: 'auto'
}
}
}
]
});
```
### Debug Mode
Enable detailed logging to troubleshoot certificate issues:
```typescript
const proxy = new SmartProxy({
enableDetailedLogging: true,
// ... other options
});
```
## Dynamic Route Updates
When routes are updated dynamically using `updateRoutes()`, SmartProxy maintains certificate management continuity:
```typescript
// Update routes with new domains
await proxy.updateRoutes([
{
name: 'new-domain',
match: { ports: 443, domains: 'newsite.example.com' },
action: {
type: 'forward',
target: { host: 'localhost', port: 8080 },
tls: {
mode: 'terminate',
certificate: 'auto' // Will use global ACME config
}
}
}
]);
```
### Important Notes on Route Updates
1. **Certificate Manager Recreation**: When routes are updated, the certificate manager is recreated to reflect the new configuration
2. **ACME Callbacks Preserved**: The ACME route update callback is automatically preserved during route updates
3. **Existing Certificates**: Certificates already provisioned are retained in the certificate store
4. **New Route Certificates**: New routes with `certificate: 'auto'` will trigger certificate provisioning
### ACME Challenge Route Lifecycle
SmartProxy v19.2.3+ implements an improved challenge route lifecycle to prevent port conflicts:
1. **Single Challenge Route**: The ACME challenge route on port 80 is added once during initialization, not per certificate
2. **Persistent During Provisioning**: The challenge route remains active throughout the entire certificate provisioning process
3. **Concurrency Protection**: Certificate provisioning is serialized to prevent race conditions
4. **Automatic Cleanup**: The challenge route is automatically removed when the certificate manager stops
### Troubleshooting Port 80 Conflicts
If you encounter "EADDRINUSE" errors on port 80:
1. **Check Existing Services**: Ensure no other service is using port 80
2. **Verify Configuration**: Confirm your ACME configuration specifies the correct port
3. **Monitor Logs**: Check for "Challenge route already active" messages
4. **Restart Clean**: If issues persist, restart SmartProxy to reset state
### Route Update Best Practices
1. **Batch Updates**: Update multiple routes in a single `updateRoutes()` call for efficiency
2. **Monitor Certificate Status**: Check certificate status after route updates
3. **Handle ACME Errors**: Implement error handling for certificate provisioning failures
4. **Test Updates**: Test route updates in staging environment first
5. **Check Port Availability**: Ensure port 80 is available before enabling ACME
## Best Practices
1. **Always test with staging ACME servers first**
2. **Set up monitoring for certificate expiration**
3. **Use meaningful route names for easier certificate management**
4. **Store static certificates securely with appropriate permissions**
5. **Implement certificate status monitoring in production**
6. **Batch route updates when possible to minimize disruption**
7. **Monitor certificate provisioning after route updates**

View File

@ -1,106 +0,0 @@
# HTTP-01 ACME Challenge Fix (v19.3.8)
## Problem Description
In SmartProxy v19.3.7 and earlier, ACME HTTP-01 challenges would fail when port 80 was configured to use HttpProxy via the `useHttpProxy` configuration option. The issue was that non-TLS connections on ports listed in `useHttpProxy` were not being forwarded to HttpProxy, instead being handled as direct connections.
## Root Cause
The bug was located in the `RouteConnectionHandler.handleForwardAction` method in `ts/proxies/smart-proxy/route-connection-handler.ts`. The method only forwarded connections to HttpProxy if they had TLS settings with mode 'terminate' or 'terminate-and-reencrypt'. Non-TLS connections (like HTTP on port 80) were always handled as direct connections, regardless of the `useHttpProxy` configuration.
## Solution
The fix adds a check for non-TLS connections on ports listed in the `useHttpProxy` array:
```typescript
// No TLS settings - check if this port should use HttpProxy
const isHttpProxyPort = this.settings.useHttpProxy?.includes(record.localPort);
if (isHttpProxyPort && this.httpProxyBridge.getHttpProxy()) {
// Forward non-TLS connections to HttpProxy if configured
if (this.settings.enableDetailedLogging) {
console.log(
`[${connectionId}] Using HttpProxy for non-TLS connection on port ${record.localPort}`
);
}
this.httpProxyBridge.forwardToHttpProxy(
connectionId,
socket,
record,
initialChunk,
this.settings.httpProxyPort || 8443,
(reason) => this.connectionManager.initiateCleanupOnce(record, reason)
);
return;
}
```
## Configuration
To enable ACME HTTP-01 challenges on port 80 with HttpProxy:
```typescript
const proxy = new SmartProxy({
useHttpProxy: [80], // Must include port 80 for HTTP-01 challenges
httpProxyPort: 8443, // Default HttpProxy port
acme: {
email: 'ssl@example.com',
port: 80, // ACME challenge port
useProduction: false
},
routes: [
{
name: 'https-route',
match: {
ports: 443,
domains: 'example.com'
},
action: {
type: 'forward',
target: { host: 'localhost', port: 8080 },
tls: {
mode: 'terminate',
certificate: 'auto',
acme: {
email: 'ssl@example.com',
useProduction: false
}
}
}
}
]
});
```
## Testing
The fix is verified by unit tests in `test/test.http-fix-unit.ts`:
1. **Test 1**: Verifies that non-TLS connections on ports in `useHttpProxy` are forwarded to HttpProxy
2. **Test 2**: Confirms that ports not in `useHttpProxy` still use direct connections
3. **Test 3**: Validates that ACME HTTP-01 challenges on port 80 work correctly with HttpProxy
## Impact
This fix enables proper ACME HTTP-01 challenge handling when:
1. Port 80 is configured in the `useHttpProxy` array
2. An ACME challenge route is configured to use HTTP-01 validation
3. Certificate provisioning with `certificate: 'auto'` is used
Without this fix, HTTP-01 challenges would fail because the challenge requests would not reach the ACME handler in HttpProxy.
## Migration Guide
If you were experiencing ACME HTTP-01 challenge failures:
1. Update to SmartProxy v19.3.8 or later
2. Ensure port 80 is included in your `useHttpProxy` configuration
3. Verify your ACME configuration includes the correct email and port settings
4. Test certificate renewal with staging ACME first (`useProduction: false`)
## Related Issues
- ACME HTTP-01 challenges timing out on port 80
- HTTP requests not being parsed on configured HttpProxy ports
- Certificate provisioning failing with "connection timeout" errors

View File

@ -1,126 +0,0 @@
# Port 80 ACME Management in SmartProxy
## Overview
SmartProxy correctly handles port management when both user routes and ACME challenges need to use the same port (typically port 80). This document explains how the system prevents port conflicts and EADDRINUSE errors.
## Port Deduplication
SmartProxy's PortManager implements automatic port deduplication:
```typescript
public async addPort(port: number): Promise<void> {
// Check if we're already listening on this port
if (this.servers.has(port)) {
console.log(`PortManager: Already listening on port ${port}`);
return;
}
// Create server for this port...
}
```
This means that when both a user route and ACME challenges are configured to use port 80, the port is only opened once and shared between both use cases.
## ACME Challenge Route Flow
1. **Initialization**: When SmartProxy starts and detects routes with `certificate: 'auto'`, it initializes the certificate manager
2. **Challenge Route Creation**: The certificate manager creates a special challenge route on the configured ACME port (default 80)
3. **Route Update**: The challenge route is added via `updateRoutes()`, which triggers port allocation
4. **Deduplication**: If port 80 is already in use by a user route, the PortManager's deduplication prevents double allocation
5. **Shared Access**: Both user routes and ACME challenges share the same port listener
## Configuration Examples
### Shared Port (Recommended)
```typescript
const settings = {
routes: [
{
name: 'web-traffic',
match: {
ports: [80]
},
action: {
type: 'forward',
targetUrl: 'http://localhost:3000'
}
},
{
name: 'secure-traffic',
match: {
ports: [443]
},
action: {
type: 'forward',
targetUrl: 'https://localhost:3001',
tls: {
mode: 'terminate',
certificate: 'auto'
}
}
}
],
acme: {
email: 'your-email@example.com',
port: 80 // Same as user route - this is safe!
}
};
```
### Separate ACME Port
```typescript
const settings = {
routes: [
{
name: 'web-traffic',
match: {
ports: [80]
},
action: {
type: 'forward',
targetUrl: 'http://localhost:3000'
}
}
],
acme: {
email: 'your-email@example.com',
port: 8080 // Different port for ACME challenges
}
};
```
## Best Practices
1. **Use Default Port 80**: Let ACME use port 80 (the default) even if you have user routes on that port
2. **Priority Routing**: ACME challenge routes have high priority (1000) to ensure they take precedence
3. **Path-Based Routing**: ACME routes only match `/.well-known/acme-challenge/*` paths, avoiding conflicts
4. **Automatic Cleanup**: Challenge routes are automatically removed when not needed
## Troubleshooting
### EADDRINUSE Errors
If you see EADDRINUSE errors, check:
1. Is another process using the port?
2. Are you running multiple SmartProxy instances?
3. Is the previous instance still shutting down?
### Certificate Provisioning Issues
1. Ensure the ACME port is accessible from the internet
2. Check that DNS is properly configured for your domains
3. Verify email configuration in ACME settings
## Technical Details
The port deduplication is handled at multiple levels:
1. **PortManager Level**: Checks if port is already active before creating new listener
2. **RouteManager Level**: Tracks which ports are needed and updates accordingly
3. **Certificate Manager Level**: Adds challenge route only when needed
This multi-level approach ensures robust port management without conflicts.

View File

@ -1,468 +0,0 @@
# SmartProxy Port Handling
This document covers all the port handling capabilities in SmartProxy, including port range specification, dynamic port mapping, and runtime port management.
## Port Range Syntax
SmartProxy offers flexible port range specification through the `TPortRange` type, which can be defined in three different ways:
### 1. Single Port
```typescript
// Match a single port
{
match: {
ports: 443
}
}
```
### 2. Array of Specific Ports
```typescript
// Match multiple specific ports
{
match: {
ports: [80, 443, 8080]
}
}
```
### 3. Port Range
```typescript
// Match a range of ports
{
match: {
ports: [{ from: 8000, to: 8100 }]
}
}
```
### 4. Mixed Port Specifications
You can combine different port specification methods in a single rule:
```typescript
// Match both specific ports and port ranges
{
match: {
ports: [80, 443, { from: 8000, to: 8100 }]
}
}
```
## Port Forwarding Options
SmartProxy offers several ways to handle port forwarding from source to target:
### 1. Static Port Forwarding
Forward to a fixed target port:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: 8080
}
}
}
```
### 2. Preserve Source Port
Forward to the same port on the target:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: 'preserve'
}
}
}
```
### 3. Dynamic Port Mapping
Use a function to determine the target port based on connection context:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: (context) => {
// Calculate port based on request details
return 8000 + (context.port % 100);
}
}
}
}
```
## Port Selection Context
When using dynamic port mapping functions, you have access to a rich context object that provides details about the connection:
```typescript
interface IRouteContext {
// Connection information
port: number; // The matched incoming port
domain?: string; // The domain from SNI or Host header
clientIp: string; // The client's IP address
serverIp: string; // The server's IP address
path?: string; // URL path (for HTTP connections)
query?: string; // Query string (for HTTP connections)
headers?: Record<string, string>; // HTTP headers (for HTTP connections)
// TLS information
isTls: boolean; // Whether the connection is TLS
tlsVersion?: string; // TLS version if applicable
// Route information
routeName?: string; // The name of the matched route
routeId?: string; // The ID of the matched route
// Additional properties
timestamp: number; // The request timestamp
connectionId: string; // Unique connection identifier
}
```
## Common Port Mapping Patterns
### 1. Port Offset Mapping
Forward traffic to target ports with a fixed offset:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: (context) => context.port + 1000
}
}
}
```
### 2. Domain-Based Port Mapping
Forward to different backend ports based on the domain:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: (context) => {
switch (context.domain) {
case 'api.example.com': return 8001;
case 'admin.example.com': return 8002;
case 'staging.example.com': return 8003;
default: return 8000;
}
}
}
}
}
```
### 3. Load Balancing with Hash-Based Distribution
Distribute connections across a port range using a deterministic hash function:
```typescript
{
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: (context) => {
// Simple hash function to ensure consistent mapping
const hostname = context.domain || '';
const hash = hostname.split('').reduce((a, b) => a + b.charCodeAt(0), 0);
return 8000 + (hash % 10); // Map to ports 8000-8009
}
}
}
}
```
## IPv6-Mapped IPv4 Compatibility
SmartProxy automatically handles IPv6-mapped IPv4 addresses for optimal compatibility. When a connection from an IPv4 address (e.g., `192.168.1.1`) arrives as an IPv6-mapped address (`::ffff:192.168.1.1`), the system normalizes these addresses for consistent matching.
This is particularly important when:
1. Matching client IP restrictions in route configurations
2. Preserving source IP for outgoing connections
3. Tracking connections and rate limits
No special configuration is needed - the system handles this normalization automatically.
## Dynamic Port Management
SmartProxy allows for runtime port configuration changes without requiring a restart.
### Adding and Removing Ports
```typescript
// Get the SmartProxy instance
const proxy = new SmartProxy({ /* config */ });
// Add a new listening port
await proxy.addListeningPort(8081);
// Remove a listening port
await proxy.removeListeningPort(8082);
```
### Runtime Route Updates
```typescript
// Get current routes
const currentRoutes = proxy.getRoutes();
// Add new route for the new port
const newRoute = {
name: 'New Dynamic Route',
match: {
ports: 8081,
domains: ['dynamic.example.com']
},
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: 9000
}
}
};
// Update the route configuration
await proxy.updateRoutes([...currentRoutes, newRoute]);
// Remove routes for a specific port
const routesWithout8082 = currentRoutes.filter(route => {
const ports = proxy.routeManager.expandPortRange(route.match.ports);
return !ports.includes(8082);
});
await proxy.updateRoutes(routesWithout8082);
```
## Performance Considerations
### Port Range Expansion
When using large port ranges, SmartProxy uses internal caching to optimize performance. For example, a range like `{ from: 1000, to: 2000 }` is expanded only once and then cached for future use.
### Port Range Validation
The system automatically validates port ranges to ensure:
1. Port numbers are within the valid range (1-65535)
2. The "from" value is not greater than the "to" value in range specifications
3. Port ranges do not contain duplicate entries
Invalid port ranges will be logged as warnings and skipped during configuration.
## Configuration Recipes
### Global Port Range
Listen on a large range of ports and forward to the same ports on a backend:
```typescript
{
name: 'Global port range forwarding',
match: {
ports: [{ from: 8000, to: 9000 }]
},
action: {
type: 'forward',
target: {
host: 'backend.example.com',
port: 'preserve'
}
}
}
```
### Domain-Specific Port Ranges
Different port ranges for different domain groups:
```typescript
[
{
name: 'API port range',
match: {
ports: [{ from: 8000, to: 8099 }]
},
action: {
type: 'forward',
target: {
host: 'api.backend.example.com',
port: 'preserve'
}
}
},
{
name: 'Admin port range',
match: {
ports: [{ from: 9000, to: 9099 }]
},
action: {
type: 'forward',
target: {
host: 'admin.backend.example.com',
port: 'preserve'
}
}
}
]
```
### Mixed Internal/External Port Forwarding
Forward specific high-numbered ports to standard ports on internal servers:
```typescript
[
{
name: 'Web server forwarding',
match: {
ports: [8080, 8443]
},
action: {
type: 'forward',
target: {
host: 'web.internal',
port: (context) => context.port === 8080 ? 80 : 443
}
}
},
{
name: 'Database forwarding',
match: {
ports: [15432]
},
action: {
type: 'forward',
target: {
host: 'db.internal',
port: 5432
}
}
}
]
```
## Debugging Port Configurations
When troubleshooting port forwarding issues, enable detailed logging:
```typescript
const proxy = new SmartProxy({
routes: [ /* your routes */ ],
enableDetailedLogging: true
});
```
This will log:
- Port configuration during startup
- Port matching decisions during routing
- Dynamic port function results
- Connection details including source and target ports
## Port Security Considerations
### Restricting Ports
For security, you may want to restrict which ports can be accessed by specific clients:
```typescript
{
name: 'Restricted port range',
match: {
ports: [{ from: 8000, to: 9000 }],
clientIp: ['10.0.0.0/8'] // Only internal network can access these ports
},
action: {
type: 'forward',
target: {
host: 'internal.example.com',
port: 'preserve'
}
}
}
```
### Rate Limiting by Port
Apply different rate limits for different port ranges:
```typescript
{
name: 'API ports with rate limiting',
match: {
ports: [{ from: 8000, to: 8100 }]
},
action: {
type: 'forward',
target: {
host: 'api.example.com',
port: 'preserve'
},
security: {
rateLimit: {
enabled: true,
maxRequests: 100,
window: 60 // 60 seconds
}
}
}
}
```
## Best Practices
1. **Use Specific Port Ranges**: Instead of large ranges (e.g., 1-65535), use specific ranges for specific purposes
2. **Prioritize Routes**: When multiple routes could match, use the `priority` field to ensure the most specific route is matched first
3. **Name Your Routes**: Use descriptive names to make debugging easier, especially when using port ranges
4. **Use Preserve Port Where Possible**: Using `port: 'preserve'` is more efficient and easier to maintain than creating multiple specific mappings
5. **Limit Dynamic Port Functions**: While powerful, complex port functions can be harder to debug; prefer simple map or math-based functions
6. **Use Port Variables**: For complex setups, define your port ranges as variables for easier maintenance:
```typescript
const API_PORTS = [{ from: 8000, to: 8099 }];
const ADMIN_PORTS = [{ from: 9000, to: 9099 }];
const routes = [
{
name: 'API Routes',
match: { ports: API_PORTS, /* ... */ },
// ...
},
{
name: 'Admin Routes',
match: { ports: ADMIN_PORTS, /* ... */ },
// ...
}
];
```

View File

@ -1,119 +0,0 @@
/**
* Certificate Management Example (v19+)
*
* This example demonstrates the new global ACME configuration introduced in v19+
* along with route-level overrides for specific domains.
*/
import {
SmartProxy,
createHttpRoute,
createHttpsTerminateRoute,
createCompleteHttpsServer
} from '../dist_ts/index.js';
async function main() {
// Create a SmartProxy instance with global ACME configuration
const proxy = new SmartProxy({
// Global ACME configuration (v19+)
// These settings apply to all routes with certificate: 'auto'
acme: {
email: 'ssl@bleu.de', // Global contact email
useProduction: false, // Use staging by default
port: 8080, // Use non-privileged port
renewThresholdDays: 30, // Renew 30 days before expiry
autoRenew: true, // Enable automatic renewal
renewCheckIntervalHours: 12 // Check twice daily
},
routes: [
// Route that uses global ACME settings
createHttpsTerminateRoute('app.example.com',
{ host: 'localhost', port: 3000 },
{ certificate: 'auto' } // Uses global ACME configuration
),
// Route with route-level ACME override
{
name: 'production-api',
match: { ports: 443, domains: 'api.example.com' },
action: {
type: 'forward',
target: { host: 'localhost', port: 3001 },
tls: {
mode: 'terminate',
certificate: 'auto',
acme: {
email: 'api-certs@example.com', // Override email
useProduction: true, // Use production for API
renewThresholdDays: 60 // Earlier renewal for critical API
}
}
}
},
// Complete HTTPS server with automatic redirects
...createCompleteHttpsServer('website.example.com',
{ host: 'localhost', port: 8080 },
{ certificate: 'auto' }
),
// Static certificate (not using ACME)
{
name: 'internal-service',
match: { ports: 8443, domains: 'internal.local' },
action: {
type: 'forward',
target: { host: 'localhost', port: 3002 },
tls: {
mode: 'terminate',
certificate: {
cert: '-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----',
key: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----'
}
}
}
}
]
});
// Monitor certificate events
proxy.on('certificate:issued', (event) => {
console.log(`Certificate issued for ${event.domain}`);
console.log(`Expires: ${event.expiryDate}`);
});
proxy.on('certificate:renewed', (event) => {
console.log(`Certificate renewed for ${event.domain}`);
});
proxy.on('certificate:error', (event) => {
console.error(`Certificate error for ${event.domain}: ${event.error}`);
});
// Start the proxy
await proxy.start();
console.log('SmartProxy started with global ACME configuration');
// Check certificate status programmatically
setTimeout(async () => {
// Get status for a specific route
const status = proxy.getCertificateStatus('app-route');
console.log('Certificate status:', status);
// Manually trigger renewal if needed
if (status && status.status === 'expiring') {
await proxy.renewCertificate('app-route');
}
}, 10000);
// Handle shutdown gracefully
process.on('SIGINT', async () => {
console.log('Shutting down proxy...');
await proxy.stop();
process.exit(0);
});
}
// Run the example
main().catch(console.error);

View File

@ -1,188 +0,0 @@
/**
* Complete SmartProxy Example (v19+)
*
* This comprehensive example demonstrates all major features of SmartProxy v19+:
* - Global ACME configuration
* - Route-based configuration
* - Helper functions for common patterns
* - Dynamic route management
* - Certificate status monitoring
* - Error handling and recovery
*/
import {
SmartProxy,
createHttpRoute,
createHttpsTerminateRoute,
createHttpsPassthroughRoute,
createHttpToHttpsRedirect,
createCompleteHttpsServer,
createLoadBalancerRoute,
createApiRoute,
createWebSocketRoute,
createStaticFileRoute,
createNfTablesRoute
} from '../dist_ts/index.js';
async function main() {
// Create SmartProxy with comprehensive configuration
const proxy = new SmartProxy({
// Global ACME configuration (v19+)
acme: {
email: 'ssl@bleu.de',
useProduction: false, // Use staging for this example
port: 8080, // Non-privileged port for development
autoRenew: true,
renewCheckIntervalHours: 12
},
// Initial routes
routes: [
// Basic HTTP service
createHttpRoute('api.example.com', { host: 'localhost', port: 3000 }),
// HTTPS with automatic certificates
createHttpsTerminateRoute('secure.example.com',
{ host: 'localhost', port: 3001 },
{ certificate: 'auto' }
),
// Complete HTTPS server with HTTP->HTTPS redirect
...createCompleteHttpsServer('www.example.com',
{ host: 'localhost', port: 8080 },
{ certificate: 'auto' }
),
// Load balancer with multiple backends
createLoadBalancerRoute(
'app.example.com',
['10.0.0.1', '10.0.0.2', '10.0.0.3'],
8080,
{
tls: {
mode: 'terminate',
certificate: 'auto'
}
}
),
// API route with CORS
createApiRoute('api.example.com', '/v1',
{ host: 'api-backend', port: 8081 },
{
useTls: true,
certificate: 'auto',
addCorsHeaders: true
}
),
// WebSocket support
createWebSocketRoute('ws.example.com', '/socket',
{ host: 'websocket-server', port: 8082 },
{
useTls: true,
certificate: 'auto'
}
),
// Static file server
createStaticFileRoute(['cdn.example.com', 'static.example.com'],
'/var/www/static',
{
serveOnHttps: true,
certificate: 'auto'
}
),
// HTTPS passthrough for services that handle their own TLS
createHttpsPassthroughRoute('legacy.example.com',
{ host: '192.168.1.100', port: 443 }
),
// HTTP to HTTPS redirects
createHttpToHttpsRedirect(['*.example.com', 'example.com'])
],
// Enable detailed logging for debugging
enableDetailedLogging: true
});
// Event handlers
proxy.on('connection', (event) => {
console.log(`New connection: ${event.source} -> ${event.destination}`);
});
proxy.on('certificate:issued', (event) => {
console.log(`Certificate issued for ${event.domain}`);
});
proxy.on('certificate:renewed', (event) => {
console.log(`Certificate renewed for ${event.domain}`);
});
proxy.on('error', (error) => {
console.error('Proxy error:', error);
});
// Start the proxy
await proxy.start();
console.log('SmartProxy started successfully');
console.log('Listening on ports:', proxy.getListeningPorts());
// Demonstrate dynamic route management
setTimeout(async () => {
console.log('Adding new route dynamically...');
// Get current routes and add a new one
const currentRoutes = proxy.settings.routes;
const newRoutes = [
...currentRoutes,
createHttpsTerminateRoute('new-service.example.com',
{ host: 'localhost', port: 3003 },
{ certificate: 'auto' }
)
];
// Update routes
await proxy.updateRoutes(newRoutes);
console.log('New route added successfully');
}, 5000);
// Check certificate status periodically
setInterval(async () => {
const routes = proxy.settings.routes;
for (const route of routes) {
if (route.action.tls?.certificate === 'auto') {
const status = proxy.getCertificateStatus(route.name);
if (status) {
console.log(`Certificate status for ${route.name}:`, status);
// Renew if expiring soon
if (status.status === 'expiring') {
console.log(`Renewing certificate for ${route.name}...`);
await proxy.renewCertificate(route.name);
}
}
}
}
}, 3600000); // Check every hour
// Graceful shutdown
process.on('SIGINT', async () => {
console.log('Shutting down gracefully...');
await proxy.stop();
process.exit(0);
});
process.on('SIGTERM', async () => {
console.log('Received SIGTERM, shutting down...');
await proxy.stop();
process.exit(0);
});
}
// Run the example
main().catch((error) => {
console.error('Failed to start proxy:', error);
process.exit(1);
});

View File

@ -1,129 +0,0 @@
/**
* Dynamic Port Management Example
*
* This example demonstrates how to dynamically add and remove ports
* while SmartProxy is running, without requiring a restart.
* Also shows the new v19+ global ACME configuration.
*/
import { SmartProxy, createHttpRoute, createHttpsTerminateRoute } from '../dist_ts/index.js';
import type { IRouteConfig } from '../dist_ts/index.js';
async function main() {
// Create a SmartProxy instance with initial routes and global ACME config
const proxy = new SmartProxy({
// Global ACME configuration (v19+)
acme: {
email: 'ssl@bleu.de',
useProduction: false,
port: 8080 // Using non-privileged port for ACME challenges
},
routes: [
// Initial route on port 8080
createHttpRoute(['example.com', '*.example.com'], { host: 'localhost', port: 3000 })
]
});
// Start the proxy
await proxy.start();
console.log('SmartProxy started with initial configuration');
console.log('Listening on ports:', proxy.getListeningPorts());
// Wait 3 seconds
console.log('Waiting 3 seconds before adding a new port...');
await new Promise(resolve => setTimeout(resolve, 3000));
// Add a new port listener without changing routes yet
await proxy.addListeningPort(8081);
console.log('Added port 8081 without any routes yet');
console.log('Now listening on ports:', proxy.getListeningPorts());
// Wait 3 more seconds
console.log('Waiting 3 seconds before adding a route for the new port...');
await new Promise(resolve => setTimeout(resolve, 3000));
// Get current routes and add a new one for port 8081
const currentRoutes = proxy.settings.routes;
// Create a new route for port 8081
const newRoute: IRouteConfig = {
match: {
ports: 8081,
domains: ['api.example.com']
},
action: {
type: 'forward' as const,
target: { host: 'localhost', port: 4000 }
},
name: 'API Route'
};
// Update routes to include the new one
await proxy.updateRoutes([...currentRoutes, newRoute]);
console.log('Added new route for port 8081');
// Wait 3 more seconds
console.log('Waiting 3 seconds before adding another port through updateRoutes...');
await new Promise(resolve => setTimeout(resolve, 3000));
// Add a completely new port via updateRoutes, which will automatically start listening
const thirdRoute: IRouteConfig = {
match: {
ports: 8082,
domains: ['admin.example.com']
},
action: {
type: 'forward' as const,
target: { host: 'localhost', port: 5000 }
},
name: 'Admin Route'
};
// Update routes again to include the third route
await proxy.updateRoutes([...currentRoutes, newRoute, thirdRoute]);
console.log('Added new route for port 8082 through updateRoutes');
console.log('Now listening on ports:', proxy.getListeningPorts());
// Wait 3 more seconds
console.log('Waiting 3 seconds before removing port 8081...');
await new Promise(resolve => setTimeout(resolve, 3000));
// Remove a port without changing routes
await proxy.removeListeningPort(8081);
console.log('Removed port 8081 (but route still exists)');
console.log('Now listening on ports:', proxy.getListeningPorts());
// Wait 3 more seconds
console.log('Waiting 3 seconds before stopping all routes on port 8082...');
await new Promise(resolve => setTimeout(resolve, 3000));
// Remove all routes for port 8082
const routesWithout8082 = currentRoutes.filter(route => {
// Check if this route includes port 8082
const ports = proxy.routeManager.expandPortRange(route.match.ports);
return !ports.includes(8082);
});
// Update routes without any for port 8082
await proxy.updateRoutes([...routesWithout8082, newRoute]);
console.log('Removed routes for port 8082 through updateRoutes');
console.log('Now listening on ports:', proxy.getListeningPorts());
// Show statistics
console.log('Statistics:', proxy.getStatistics());
// Wait 3 more seconds, then shut down
console.log('Waiting 3 seconds before shutdown...');
await new Promise(resolve => setTimeout(resolve, 3000));
// Stop the proxy
await proxy.stop();
console.log('SmartProxy stopped');
}
// Run the example
main().catch(err => {
console.error('Error in example:', err);
process.exit(1);
});

View File

@ -1,222 +0,0 @@
/**
* NFTables Integration Example
*
* This example demonstrates how to use the NFTables forwarding engine with SmartProxy
* for high-performance network routing that operates at the kernel level.
*
* NOTE: This requires elevated privileges to run (sudo) as it interacts with nftables.
* Also shows the new v19+ global ACME configuration.
*/
import { SmartProxy } from '../ts/proxies/smart-proxy/index.js';
import {
createNfTablesRoute,
createNfTablesTerminateRoute,
createCompleteNfTablesHttpsServer
} from '../ts/proxies/smart-proxy/utils/route-helpers.js';
// Simple NFTables-based HTTP forwarding example
async function simpleForwardingExample() {
console.log('Starting simple NFTables forwarding example...');
// Create a SmartProxy instance with a simple NFTables route
const proxy = new SmartProxy({
routes: [
createNfTablesRoute('example.com', {
host: 'localhost',
port: 8080
}, {
ports: 80,
protocol: 'tcp',
preserveSourceIP: true,
tableName: 'smartproxy_example'
})
],
enableDetailedLogging: true
});
// Start the proxy
await proxy.start();
console.log('NFTables proxy started. Press Ctrl+C to stop.');
// Handle shutdown
process.on('SIGINT', async () => {
console.log('Stopping proxy...');
await proxy.stop();
process.exit(0);
});
}
// HTTPS termination example with NFTables
async function httpsTerminationExample() {
console.log('Starting HTTPS termination with NFTables example...');
// Create a SmartProxy instance with global ACME and NFTables HTTPS termination
const proxy = new SmartProxy({
// Global ACME configuration (v19+)
acme: {
email: 'ssl@bleu.de',
useProduction: false,
port: 80 // NFTables needs root, so we can use port 80
},
routes: [
createNfTablesTerminateRoute('secure.example.com', {
host: 'localhost',
port: 8443
}, {
ports: 443,
certificate: 'auto', // Uses global ACME configuration
tableName: 'smartproxy_https'
})
],
enableDetailedLogging: true
});
// Start the proxy
await proxy.start();
console.log('HTTPS termination proxy started. Press Ctrl+C to stop.');
// Handle shutdown
process.on('SIGINT', async () => {
console.log('Stopping proxy...');
await proxy.stop();
process.exit(0);
});
}
// Complete HTTPS server with HTTP redirects using NFTables
async function completeHttpsServerExample() {
console.log('Starting complete HTTPS server with NFTables example...');
// Create a SmartProxy instance with a complete HTTPS server
const proxy = new SmartProxy({
routes: createCompleteNfTablesHttpsServer('complete.example.com', {
host: 'localhost',
port: 8443
}, {
certificate: 'auto',
tableName: 'smartproxy_complete'
}),
enableDetailedLogging: true
});
// Start the proxy
await proxy.start();
console.log('Complete HTTPS server started. Press Ctrl+C to stop.');
// Handle shutdown
process.on('SIGINT', async () => {
console.log('Stopping proxy...');
await proxy.stop();
process.exit(0);
});
}
// Load balancing example with NFTables
async function loadBalancingExample() {
console.log('Starting load balancing with NFTables example...');
// Create a SmartProxy instance with a load balancing configuration
const proxy = new SmartProxy({
routes: [
createNfTablesRoute('lb.example.com', {
// NFTables will automatically distribute connections to these hosts
host: 'backend1.example.com',
port: 8080
}, {
ports: 80,
tableName: 'smartproxy_lb'
})
],
enableDetailedLogging: true
});
// Start the proxy
await proxy.start();
console.log('Load balancing proxy started. Press Ctrl+C to stop.');
// Handle shutdown
process.on('SIGINT', async () => {
console.log('Stopping proxy...');
await proxy.stop();
process.exit(0);
});
}
// Advanced example with QoS and security settings
async function advancedExample() {
console.log('Starting advanced NFTables example with QoS and security...');
// Create a SmartProxy instance with advanced settings
const proxy = new SmartProxy({
routes: [
createNfTablesRoute('advanced.example.com', {
host: 'localhost',
port: 8080
}, {
ports: 80,
protocol: 'tcp',
preserveSourceIP: true,
maxRate: '10mbps', // QoS rate limiting
priority: 2, // QoS priority (1-10, lower is higher priority)
ipAllowList: ['192.168.1.0/24'], // Only allow this subnet
ipBlockList: ['192.168.1.100'], // Block this specific IP
useIPSets: true, // Use IP sets for more efficient rule processing
useAdvancedNAT: true, // Use connection tracking for stateful NAT
tableName: 'smartproxy_advanced'
})
],
enableDetailedLogging: true
});
// Start the proxy
await proxy.start();
console.log('Advanced NFTables proxy started. Press Ctrl+C to stop.');
// Handle shutdown
process.on('SIGINT', async () => {
console.log('Stopping proxy...');
await proxy.stop();
process.exit(0);
});
}
// Run one of the examples based on the command line argument
async function main() {
const example = process.argv[2] || 'simple';
switch (example) {
case 'simple':
await simpleForwardingExample();
break;
case 'https':
await httpsTerminationExample();
break;
case 'complete':
await completeHttpsServerExample();
break;
case 'lb':
await loadBalancingExample();
break;
case 'advanced':
await advancedExample();
break;
default:
console.error('Unknown example:', example);
console.log('Available examples: simple, https, complete, lb, advanced');
process.exit(1);
}
}
// Check if running as root/sudo
if (process.getuid && process.getuid() !== 0) {
console.error('This example requires root privileges to modify nftables rules.');
console.log('Please run with sudo: sudo tsx examples/nftables-integration.ts');
process.exit(1);
}
main().catch(err => {
console.error('Error running example:', err);
process.exit(1);
});

View File

@ -27,6 +27,7 @@
"@push.rocks/smartcrypto": "^2.0.4",
"@push.rocks/smartdelay": "^3.0.5",
"@push.rocks/smartfile": "^11.2.0",
"@push.rocks/smartlog": "^3.1.2",
"@push.rocks/smartnetwork": "^4.0.2",
"@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrequest": "^2.1.0",

149
pnpm-lock.yaml generated
View File

@ -23,6 +23,9 @@ importers:
'@push.rocks/smartfile':
specifier: ^11.2.0
version: 11.2.0
'@push.rocks/smartlog':
specifier: ^3.1.2
version: 3.1.2
'@push.rocks/smartnetwork':
specifier: ^4.0.2
version: 4.0.2
@ -902,12 +905,6 @@ packages:
'@push.rocks/smartlog-interfaces@3.0.2':
resolution: {integrity: sha512-8hGRTJehbsFSJxLhCQkA018mZtXVPxPTblbg9VaE/EqISRzUw+eosJ2EJV7M4Qu0eiTJZjnWnNLn8CkD77ziWw==}
'@push.rocks/smartlog@3.0.7':
resolution: {integrity: sha512-WHOw0iHHjCEbYY4KGX40iFtLI11QJvvWIbC9yFn3Mt+nrdupMnry7Ztc5v/PqO8lu33Q6xDBMXiNQ9yNY0HVGw==}
'@push.rocks/smartlog@3.0.9':
resolution: {integrity: sha512-B/YIJrwXsbxPkAJly8+55yx3Eqm5bIaCZ/xD2oe6fD8Zp58VLF2P8hpoQZJOiSO+KI7wXVlTEFHsmt8fpRZIVA==}
'@push.rocks/smartlog@3.1.2':
resolution: {integrity: sha512-krjWramvM8R+dY69KoBBsUtsMHKtw7eCdvcg/uYsU6e8gzOfGiQOuWeat39d6doPHbzGuxh6lSOWGUpUTTu6aw==}
@ -1931,10 +1928,6 @@ packages:
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
engines: {node: '>=4'}
chalk@5.4.1:
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
character-entities-html4@2.1.0:
resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==}
@ -1965,14 +1958,6 @@ packages:
resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==}
engines: {node: '>=12'}
cli-cursor@5.0.0:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
engines: {node: '>=18'}
cli-spinners@2.9.2:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@ -2231,9 +2216,6 @@ packages:
elliptic@6.6.1:
resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==}
emoji-regex@10.4.0:
resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==}
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@ -2520,10 +2502,6 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
get-east-asian-width@1.3.0:
resolution: {integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==}
engines: {node: '>=18'}
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@ -2730,10 +2708,6 @@ packages:
resolution: {integrity: sha1-bKiwe5nHeZgCWQDlVc7Y7YCHmoM=}
engines: {node: '>=0.10.0'}
is-interactive@2.0.0:
resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
engines: {node: '>=12'}
is-ip@4.0.0:
resolution: {integrity: sha512-4B4XA2HEIm/PY+OSpeMBXr8pGWBYbXuHgjMAqrwbLO3CPTCAd9ArEJzBUKGZtk9viY6+aSfadGnWyjY3ydYZkw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -2774,10 +2748,6 @@ packages:
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
engines: {node: '>=18'}
is-unicode-supported@1.3.0:
resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
engines: {node: '>=12'}
is-unicode-supported@2.1.0:
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
engines: {node: '>=18'}
@ -2937,10 +2907,6 @@ packages:
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
log-symbols@6.0.0:
resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
engines: {node: '>=18'}
logform@2.7.0:
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
engines: {node: '>= 12.0.0'}
@ -3152,10 +3118,6 @@ packages:
engines: {node: '>=16'}
hasBin: true
mimic-function@5.0.1:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'}
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@ -3337,10 +3299,6 @@ packages:
one-time@1.0.0:
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
onetime@7.0.0:
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
engines: {node: '>=18'}
only@0.0.2:
resolution: {integrity: sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=}
@ -3348,10 +3306,6 @@ packages:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
ora@8.2.0:
resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==}
engines: {node: '>=18'}
p-cancelable@3.0.0:
resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
engines: {node: '>=12.20'}
@ -3658,10 +3612,6 @@ packages:
resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==}
engines: {node: '>=14.16'}
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
rimraf@3.0.2:
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
hasBin: true
@ -3818,10 +3768,6 @@ packages:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
stdin-discarder@0.2.2:
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
engines: {node: '>=18'}
stream-shift@1.0.3:
resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==}
@ -3840,10 +3786,6 @@ packages:
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
engines: {node: '>=12'}
string-width@7.2.0:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
string_decoder@1.1.1:
resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
@ -5381,7 +5323,7 @@ snapshots:
'@push.rocks/smartcli': 4.0.11
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smartlog': 3.0.9
'@push.rocks/smartlog': 3.1.2
'@push.rocks/smartpath': 5.0.18
'@push.rocks/smartpromise': 4.2.3
typescript: 5.7.3
@ -5411,7 +5353,7 @@ snapshots:
'@push.rocks/smartcli': 4.0.11
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smartlog': 3.0.9
'@push.rocks/smartlog': 3.1.2
'@push.rocks/smartnpm': 2.0.4
'@push.rocks/smartpath': 5.0.18
'@push.rocks/smartrequest': 2.1.0
@ -5710,7 +5652,7 @@ snapshots:
'@api.global/typedrequest': 3.1.10
'@configvault.io/interfaces': 1.0.17
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartlog': 3.1.2
'@push.rocks/smartpath': 5.0.18
'@push.rocks/smartacme@8.0.0(@aws-sdk/credential-providers@3.798.0)(socks@2.8.4)':
@ -5736,7 +5678,6 @@ snapshots:
- '@mongodb-js/zstd'
- '@nuxt/kit'
- aws-crt
- bufferutil
- encoding
- gcp-metadata
- kerberos
@ -5745,7 +5686,6 @@ snapshots:
- snappy
- socks
- supports-color
- utf-8-validate
- vue
'@push.rocks/smartarchive@3.0.8':
@ -5816,7 +5756,7 @@ snapshots:
'@push.rocks/smartcli@4.0.11':
dependencies:
'@push.rocks/lik': 6.2.2
'@push.rocks/smartlog': 3.0.9
'@push.rocks/smartlog': 3.1.2
'@push.rocks/smartobject': 1.0.12
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrx': 3.0.10
@ -5841,7 +5781,7 @@ snapshots:
dependencies:
'@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartlog': 3.1.2
'@push.rocks/smartmongo': 2.0.12(@aws-sdk/credential-providers@3.798.0)(socks@2.8.4)
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrx': 3.0.10
@ -5979,25 +5919,6 @@ snapshots:
'@api.global/typedrequest-interfaces': 2.0.2
'@tsclass/tsclass': 4.4.4
'@push.rocks/smartlog@3.0.7':
dependencies:
'@push.rocks/isounique': 1.0.5
'@push.rocks/smartlog-interfaces': 3.0.2
'@push.rocks/smartlog@3.0.9':
dependencies:
'@api.global/typedrequest-interfaces': 3.0.19
'@push.rocks/consolecolor': 2.0.2
'@push.rocks/isounique': 1.0.5
'@push.rocks/smartclickhouse': 2.0.17
'@push.rocks/smartfile': 11.2.0
'@push.rocks/smarthash': 3.0.4
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smarttime': 4.1.1
'@push.rocks/webrequest': 3.0.37
'@tsclass/tsclass': 9.2.0
ora: 8.2.0
'@push.rocks/smartlog@3.1.2':
dependencies:
'@api.global/typedrequest-interfaces': 3.0.19
@ -6322,7 +6243,7 @@ snapshots:
dependencies:
'@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartlog': 3.0.7
'@push.rocks/smartlog': 3.1.2
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartrx': 3.0.10
'@push.rocks/smarttime': 4.1.1
@ -7590,8 +7511,6 @@ snapshots:
escape-string-regexp: 1.0.5
supports-color: 5.5.0
chalk@5.4.1: {}
character-entities-html4@2.1.0: {}
character-entities-legacy@3.0.0: {}
@ -7616,12 +7535,6 @@ snapshots:
dependencies:
escape-string-regexp: 5.0.0
cli-cursor@5.0.0:
dependencies:
restore-cursor: 5.1.0
cli-spinners@2.9.2: {}
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@ -7856,8 +7769,6 @@ snapshots:
minimalistic-assert: 1.0.1
minimalistic-crypto-utils: 1.0.1
emoji-regex@10.4.0: {}
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@ -8217,8 +8128,6 @@ snapshots:
get-caller-file@2.0.5: {}
get-east-asian-width@1.3.0: {}
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@ -8499,8 +8408,6 @@ snapshots:
is-gzip@1.0.0: {}
is-interactive@2.0.0: {}
is-ip@4.0.0:
dependencies:
ip-regex: 5.0.0
@ -8534,8 +8441,6 @@ snapshots:
is-stream@4.0.1: {}
is-unicode-supported@1.3.0: {}
is-unicode-supported@2.1.0: {}
is-windows@1.0.2: {}
@ -8710,11 +8615,6 @@ snapshots:
lodash@4.17.21: {}
log-symbols@6.0.0:
dependencies:
chalk: 5.4.1
is-unicode-supported: 1.3.0
logform@2.7.0:
dependencies:
'@colors/colors': 1.6.0
@ -9101,8 +9001,6 @@ snapshots:
mime@4.0.6: {}
mimic-function@5.0.1: {}
mimic-response@3.1.0: {}
mimic-response@4.0.0: {}
@ -9268,10 +9166,6 @@ snapshots:
dependencies:
fn.name: 1.1.0
onetime@7.0.0:
dependencies:
mimic-function: 5.0.1
only@0.0.2: {}
open@8.4.2:
@ -9280,18 +9174,6 @@ snapshots:
is-docker: 2.2.1
is-wsl: 2.2.0
ora@8.2.0:
dependencies:
chalk: 5.4.1
cli-cursor: 5.0.0
cli-spinners: 2.9.2
is-interactive: 2.0.0
is-unicode-supported: 2.1.0
log-symbols: 6.0.0
stdin-discarder: 0.2.2
string-width: 7.2.0
strip-ansi: 7.1.0
p-cancelable@3.0.0: {}
p-finally@1.0.0: {}
@ -9647,11 +9529,6 @@ snapshots:
dependencies:
lowercase-keys: 3.0.0
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
signal-exit: 4.1.0
rimraf@3.0.2:
dependencies:
glob: 7.2.3
@ -9866,8 +9743,6 @@ snapshots:
statuses@2.0.1: {}
stdin-discarder@0.2.2: {}
stream-shift@1.0.3: {}
streamsearch@0.1.2: {}
@ -9891,12 +9766,6 @@ snapshots:
emoji-regex: 9.2.2
strip-ansi: 7.1.0
string-width@7.2.0:
dependencies:
emoji-regex: 10.4.0
get-east-asian-width: 1.3.0
strip-ansi: 7.1.0
string_decoder@1.1.1:
dependencies:
safe-buffer: 5.1.2

View File

@ -12,3 +12,4 @@ export * from './security-utils.js';
export * from './shared-security-manager.js';
export * from './event-system.js';
export * from './websocket-utils.js';
export * from './logger.js';

10
ts/core/utils/logger.ts Normal file
View File

@ -0,0 +1,10 @@
import * as plugins from '../../plugins.js';
export const logger = new plugins.smartlog.Smartlog({
logContext: {},
minimumLogLevel: 'info',
});
logger.addLogDestination(new plugins.smartlogDestinationLocal.DestinationLocal());
logger.log('info', 'Logger initialized');

View File

@ -26,6 +26,8 @@ import * as smartcrypto from '@push.rocks/smartcrypto';
import * as smartacme from '@push.rocks/smartacme';
import * as smartacmePlugins from '@push.rocks/smartacme/dist_ts/smartacme.plugins.js';
import * as smartacmeHandlers from '@push.rocks/smartacme/dist_ts/handlers/index.js';
import * as smartlog from '@push.rocks/smartlog';
import * as smartlogDestinationLocal from '@push.rocks/smartlog/destination-local';
import * as taskbuffer from '@push.rocks/taskbuffer';
export {
@ -39,6 +41,8 @@ export {
smartacme,
smartacmePlugins,
smartacmeHandlers,
smartlog,
smartlogDestinationLocal,
taskbuffer,
};