@serve.zone/remoteingress 4.3.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_rust/remoteingress-bin_linux_amd64 +0 -0
- package/dist_rust/remoteingress-bin_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.remoteingressedge.d.ts +8 -0
- package/dist_ts/classes.remoteingressedge.js +56 -1
- package/dist_ts/classes.remoteingresshub.d.ts +17 -6
- package/dist_ts/classes.remoteingresshub.js +64 -1
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.remoteingressedge.ts +68 -0
- package/ts/classes.remoteingresshub.ts +80 -1
|
Binary file
|
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/remoteingress',
|
|
6
|
-
version: '4.
|
|
6
|
+
version: '4.4.0',
|
|
7
7
|
description: 'Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwyQkFBMkI7SUFDakMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLHlLQUF5SztDQUN2TCxDQUFBIn0=
|
|
@@ -8,6 +8,10 @@ export interface IEdgeConfig {
|
|
|
8
8
|
export declare class RemoteIngressEdge extends EventEmitter {
|
|
9
9
|
private bridge;
|
|
10
10
|
private started;
|
|
11
|
+
private stopping;
|
|
12
|
+
private savedConfig;
|
|
13
|
+
private restartBackoffMs;
|
|
14
|
+
private restartAttempts;
|
|
11
15
|
private statusInterval;
|
|
12
16
|
constructor();
|
|
13
17
|
/**
|
|
@@ -35,4 +39,8 @@ export declare class RemoteIngressEdge extends EventEmitter {
|
|
|
35
39
|
* Check if the bridge is running.
|
|
36
40
|
*/
|
|
37
41
|
get running(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Handle unexpected Rust binary crash — auto-restart with backoff.
|
|
44
|
+
*/
|
|
45
|
+
private handleCrashRecovery;
|
|
38
46
|
}
|
|
@@ -1,10 +1,57 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
3
|
import { decodeConnectionToken } from './classes.token.js';
|
|
4
|
+
const MAX_RESTART_ATTEMPTS = 10;
|
|
5
|
+
const MAX_RESTART_BACKOFF_MS = 30_000;
|
|
4
6
|
export class RemoteIngressEdge extends EventEmitter {
|
|
5
7
|
constructor() {
|
|
6
8
|
super();
|
|
7
9
|
this.started = false;
|
|
10
|
+
this.stopping = false;
|
|
11
|
+
this.savedConfig = null;
|
|
12
|
+
this.restartBackoffMs = 1000;
|
|
13
|
+
this.restartAttempts = 0;
|
|
14
|
+
/**
|
|
15
|
+
* Handle unexpected Rust binary crash — auto-restart with backoff.
|
|
16
|
+
*/
|
|
17
|
+
this.handleCrashRecovery = async (code, signal) => {
|
|
18
|
+
if (this.stopping || !this.started || !this.savedConfig) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.error(`[RemoteIngressEdge] Rust binary crashed (code=${code}, signal=${signal}), ` +
|
|
22
|
+
`attempt ${this.restartAttempts + 1}/${MAX_RESTART_ATTEMPTS}`);
|
|
23
|
+
this.started = false;
|
|
24
|
+
if (this.restartAttempts >= MAX_RESTART_ATTEMPTS) {
|
|
25
|
+
console.error('[RemoteIngressEdge] Max restart attempts reached, giving up');
|
|
26
|
+
this.emit('crashRecoveryFailed');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, this.restartBackoffMs));
|
|
30
|
+
this.restartBackoffMs = Math.min(this.restartBackoffMs * 2, MAX_RESTART_BACKOFF_MS);
|
|
31
|
+
this.restartAttempts++;
|
|
32
|
+
try {
|
|
33
|
+
const spawned = await this.bridge.spawn();
|
|
34
|
+
if (!spawned) {
|
|
35
|
+
console.error('[RemoteIngressEdge] Failed to respawn binary');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.bridge.on('exit', this.handleCrashRecovery);
|
|
39
|
+
await this.bridge.sendCommand('startEdge', {
|
|
40
|
+
hubHost: this.savedConfig.hubHost,
|
|
41
|
+
hubPort: this.savedConfig.hubPort ?? 8443,
|
|
42
|
+
edgeId: this.savedConfig.edgeId,
|
|
43
|
+
secret: this.savedConfig.secret,
|
|
44
|
+
});
|
|
45
|
+
this.started = true;
|
|
46
|
+
this.restartAttempts = 0;
|
|
47
|
+
this.restartBackoffMs = 1000;
|
|
48
|
+
console.log('[RemoteIngressEdge] Successfully recovered from crash');
|
|
49
|
+
this.emit('crashRecovered');
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
console.error(`[RemoteIngressEdge] Crash recovery failed: ${err}`);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
8
55
|
const packageDir = plugins.path.resolve(plugins.path.dirname(new URL(import.meta.url).pathname), '..');
|
|
9
56
|
this.bridge = new plugins.smartrust.RustBridge({
|
|
10
57
|
binaryName: 'remoteingress-bin',
|
|
@@ -59,10 +106,14 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
59
106
|
else {
|
|
60
107
|
edgeConfig = config;
|
|
61
108
|
}
|
|
109
|
+
this.savedConfig = edgeConfig;
|
|
110
|
+
this.stopping = false;
|
|
62
111
|
const spawned = await this.bridge.spawn();
|
|
63
112
|
if (!spawned) {
|
|
64
113
|
throw new Error('Failed to spawn remoteingress-bin');
|
|
65
114
|
}
|
|
115
|
+
// Register crash recovery handler
|
|
116
|
+
this.bridge.on('exit', this.handleCrashRecovery);
|
|
66
117
|
await this.bridge.sendCommand('startEdge', {
|
|
67
118
|
hubHost: edgeConfig.hubHost,
|
|
68
119
|
hubPort: edgeConfig.hubPort ?? 8443,
|
|
@@ -70,6 +121,8 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
70
121
|
secret: edgeConfig.secret,
|
|
71
122
|
});
|
|
72
123
|
this.started = true;
|
|
124
|
+
this.restartAttempts = 0;
|
|
125
|
+
this.restartBackoffMs = 1000;
|
|
73
126
|
// Start periodic status logging
|
|
74
127
|
this.statusInterval = setInterval(async () => {
|
|
75
128
|
try {
|
|
@@ -87,6 +140,7 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
87
140
|
* Stop the edge and kill the Rust process.
|
|
88
141
|
*/
|
|
89
142
|
async stop() {
|
|
143
|
+
this.stopping = true;
|
|
90
144
|
if (this.statusInterval) {
|
|
91
145
|
clearInterval(this.statusInterval);
|
|
92
146
|
this.statusInterval = undefined;
|
|
@@ -98,6 +152,7 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
98
152
|
catch {
|
|
99
153
|
// Process may already be dead
|
|
100
154
|
}
|
|
155
|
+
this.bridge.removeListener('exit', this.handleCrashRecovery);
|
|
101
156
|
this.bridge.kill();
|
|
102
157
|
this.started = false;
|
|
103
158
|
}
|
|
@@ -115,4 +170,4 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
115
170
|
return this.bridge.running;
|
|
116
171
|
}
|
|
117
172
|
}
|
|
118
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
173
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzZWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMucmVtb3RlaW5ncmVzc2VkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUN0QyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQXdDM0QsTUFBTSxvQkFBb0IsR0FBRyxFQUFFLENBQUM7QUFDaEMsTUFBTSxzQkFBc0IsR0FBRyxNQUFNLENBQUM7QUFFdEMsTUFBTSxPQUFPLGlCQUFrQixTQUFRLFlBQVk7SUFTakQ7UUFDRSxLQUFLLEVBQUUsQ0FBQztRQVJGLFlBQU8sR0FBRyxLQUFLLENBQUM7UUFDaEIsYUFBUSxHQUFHLEtBQUssQ0FBQztRQUNqQixnQkFBVyxHQUF1QixJQUFJLENBQUM7UUFDdkMscUJBQWdCLEdBQUcsSUFBSSxDQUFDO1FBQ3hCLG9CQUFlLEdBQUcsQ0FBQyxDQUFDO1FBMkk1Qjs7V0FFRztRQUNLLHdCQUFtQixHQUFHLEtBQUssRUFBRSxJQUFtQixFQUFFLE1BQXFCLEVBQUUsRUFBRTtZQUNqRixJQUFJLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO2dCQUN4RCxPQUFPO1lBQ1QsQ0FBQztZQUVELE9BQU8sQ0FBQyxLQUFLLENBQ1gsaURBQWlELElBQUksWUFBWSxNQUFNLEtBQUs7Z0JBQzVFLFdBQVcsSUFBSSxDQUFDLGVBQWUsR0FBRyxDQUFDLElBQUksb0JBQW9CLEVBQUUsQ0FDOUQsQ0FBQztZQUVGLElBQUksQ0FBQyxPQUFPLEdBQUcsS0FBSyxDQUFDO1lBRXJCLElBQUksSUFBSSxDQUFDLGVBQWUsSUFBSSxvQkFBb0IsRUFBRSxDQUFDO2dCQUNq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
|
|
@@ -7,9 +7,20 @@ export interface IHubConfig {
|
|
|
7
7
|
keyPem?: string;
|
|
8
8
|
};
|
|
9
9
|
}
|
|
10
|
+
type TAllowedEdge = {
|
|
11
|
+
id: string;
|
|
12
|
+
secret: string;
|
|
13
|
+
listenPorts?: number[];
|
|
14
|
+
stunIntervalSecs?: number;
|
|
15
|
+
};
|
|
10
16
|
export declare class RemoteIngressHub extends EventEmitter {
|
|
11
17
|
private bridge;
|
|
12
18
|
private started;
|
|
19
|
+
private stopping;
|
|
20
|
+
private savedConfig;
|
|
21
|
+
private savedEdges;
|
|
22
|
+
private restartBackoffMs;
|
|
23
|
+
private restartAttempts;
|
|
13
24
|
constructor();
|
|
14
25
|
/**
|
|
15
26
|
* Start the hub — spawns the Rust binary and starts the tunnel server.
|
|
@@ -22,12 +33,7 @@ export declare class RemoteIngressHub extends EventEmitter {
|
|
|
22
33
|
/**
|
|
23
34
|
* Update the list of allowed edges that can connect to this hub.
|
|
24
35
|
*/
|
|
25
|
-
updateAllowedEdges(edges:
|
|
26
|
-
id: string;
|
|
27
|
-
secret: string;
|
|
28
|
-
listenPorts?: number[];
|
|
29
|
-
stunIntervalSecs?: number;
|
|
30
|
-
}>): Promise<void>;
|
|
36
|
+
updateAllowedEdges(edges: TAllowedEdge[]): Promise<void>;
|
|
31
37
|
/**
|
|
32
38
|
* Get the current hub status.
|
|
33
39
|
*/
|
|
@@ -45,4 +51,9 @@ export declare class RemoteIngressHub extends EventEmitter {
|
|
|
45
51
|
* Check if the bridge is running.
|
|
46
52
|
*/
|
|
47
53
|
get running(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Handle unexpected Rust binary crash — auto-restart with backoff.
|
|
56
|
+
*/
|
|
57
|
+
private handleCrashRecovery;
|
|
48
58
|
}
|
|
59
|
+
export {};
|
|
@@ -1,9 +1,63 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import { EventEmitter } from 'events';
|
|
3
|
+
const MAX_RESTART_ATTEMPTS = 10;
|
|
4
|
+
const MAX_RESTART_BACKOFF_MS = 30_000;
|
|
3
5
|
export class RemoteIngressHub extends EventEmitter {
|
|
4
6
|
constructor() {
|
|
5
7
|
super();
|
|
6
8
|
this.started = false;
|
|
9
|
+
this.stopping = false;
|
|
10
|
+
this.savedConfig = null;
|
|
11
|
+
this.savedEdges = [];
|
|
12
|
+
this.restartBackoffMs = 1000;
|
|
13
|
+
this.restartAttempts = 0;
|
|
14
|
+
/**
|
|
15
|
+
* Handle unexpected Rust binary crash — auto-restart with backoff.
|
|
16
|
+
*/
|
|
17
|
+
this.handleCrashRecovery = async (code, signal) => {
|
|
18
|
+
if (this.stopping || !this.started || !this.savedConfig) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.error(`[RemoteIngressHub] Rust binary crashed (code=${code}, signal=${signal}), ` +
|
|
22
|
+
`attempt ${this.restartAttempts + 1}/${MAX_RESTART_ATTEMPTS}`);
|
|
23
|
+
this.started = false;
|
|
24
|
+
if (this.restartAttempts >= MAX_RESTART_ATTEMPTS) {
|
|
25
|
+
console.error('[RemoteIngressHub] Max restart attempts reached, giving up');
|
|
26
|
+
this.emit('crashRecoveryFailed');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, this.restartBackoffMs));
|
|
30
|
+
this.restartBackoffMs = Math.min(this.restartBackoffMs * 2, MAX_RESTART_BACKOFF_MS);
|
|
31
|
+
this.restartAttempts++;
|
|
32
|
+
try {
|
|
33
|
+
const spawned = await this.bridge.spawn();
|
|
34
|
+
if (!spawned) {
|
|
35
|
+
console.error('[RemoteIngressHub] Failed to respawn binary');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.bridge.on('exit', this.handleCrashRecovery);
|
|
39
|
+
const config = this.savedConfig;
|
|
40
|
+
await this.bridge.sendCommand('startHub', {
|
|
41
|
+
tunnelPort: config.tunnelPort ?? 8443,
|
|
42
|
+
targetHost: config.targetHost ?? '127.0.0.1',
|
|
43
|
+
...(config.tls?.certPem && config.tls?.keyPem
|
|
44
|
+
? { tlsCertPem: config.tls.certPem, tlsKeyPem: config.tls.keyPem }
|
|
45
|
+
: {}),
|
|
46
|
+
});
|
|
47
|
+
// Restore allowed edges
|
|
48
|
+
if (this.savedEdges.length > 0) {
|
|
49
|
+
await this.bridge.sendCommand('updateAllowedEdges', { edges: this.savedEdges });
|
|
50
|
+
}
|
|
51
|
+
this.started = true;
|
|
52
|
+
this.restartAttempts = 0;
|
|
53
|
+
this.restartBackoffMs = 1000;
|
|
54
|
+
console.log('[RemoteIngressHub] Successfully recovered from crash');
|
|
55
|
+
this.emit('crashRecovered');
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.error(`[RemoteIngressHub] Crash recovery failed: ${err}`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
7
61
|
const packageDir = plugins.path.resolve(plugins.path.dirname(new URL(import.meta.url).pathname), '..');
|
|
8
62
|
this.bridge = new plugins.smartrust.RustBridge({
|
|
9
63
|
binaryName: 'remoteingress-bin',
|
|
@@ -39,10 +93,14 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
39
93
|
* Start the hub — spawns the Rust binary and starts the tunnel server.
|
|
40
94
|
*/
|
|
41
95
|
async start(config = {}) {
|
|
96
|
+
this.savedConfig = config;
|
|
97
|
+
this.stopping = false;
|
|
42
98
|
const spawned = await this.bridge.spawn();
|
|
43
99
|
if (!spawned) {
|
|
44
100
|
throw new Error('Failed to spawn remoteingress-bin');
|
|
45
101
|
}
|
|
102
|
+
// Register crash recovery handler
|
|
103
|
+
this.bridge.on('exit', this.handleCrashRecovery);
|
|
46
104
|
await this.bridge.sendCommand('startHub', {
|
|
47
105
|
tunnelPort: config.tunnelPort ?? 8443,
|
|
48
106
|
targetHost: config.targetHost ?? '127.0.0.1',
|
|
@@ -51,11 +109,14 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
51
109
|
: {}),
|
|
52
110
|
});
|
|
53
111
|
this.started = true;
|
|
112
|
+
this.restartAttempts = 0;
|
|
113
|
+
this.restartBackoffMs = 1000;
|
|
54
114
|
}
|
|
55
115
|
/**
|
|
56
116
|
* Stop the hub and kill the Rust process.
|
|
57
117
|
*/
|
|
58
118
|
async stop() {
|
|
119
|
+
this.stopping = true;
|
|
59
120
|
if (this.started) {
|
|
60
121
|
try {
|
|
61
122
|
await this.bridge.sendCommand('stopHub', {});
|
|
@@ -63,6 +124,7 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
63
124
|
catch {
|
|
64
125
|
// Process may already be dead
|
|
65
126
|
}
|
|
127
|
+
this.bridge.removeListener('exit', this.handleCrashRecovery);
|
|
66
128
|
this.bridge.kill();
|
|
67
129
|
this.started = false;
|
|
68
130
|
}
|
|
@@ -71,6 +133,7 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
71
133
|
* Update the list of allowed edges that can connect to this hub.
|
|
72
134
|
*/
|
|
73
135
|
async updateAllowedEdges(edges) {
|
|
136
|
+
this.savedEdges = edges;
|
|
74
137
|
await this.bridge.sendCommand('updateAllowedEdges', { edges });
|
|
75
138
|
}
|
|
76
139
|
/**
|
|
@@ -86,4 +149,4 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
86
149
|
return this.bridge.running;
|
|
87
150
|
}
|
|
88
151
|
}
|
|
89
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
152
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzaHViLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5yZW1vdGVpbmdyZXNzaHViLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFxRHRDLE1BQU0sb0JBQW9CLEdBQUcsRUFBRSxDQUFDO0FBQ2hDLE1BQU0sc0JBQXNCLEdBQUcsTUFBTSxDQUFDO0FBRXRDLE1BQU0sT0FBTyxnQkFBaUIsU0FBUSxZQUFZO0lBU2hEO1FBQ0UsS0FBSyxFQUFFLENBQUM7UUFSRixZQUFPLEdBQUcsS0FBSyxDQUFDO1FBQ2hCLGFBQVEsR0FBRyxLQUFLLENBQUM7UUFDakIsZ0JBQVcsR0FBc0IsSUFBSSxDQUFDO1FBQ3RDLGVBQVUsR0FBbUIsRUFBRSxDQUFDO1FBQ2hDLHFCQUFnQixHQUFHLElBQUksQ0FBQztRQUN4QixvQkFBZSxHQUFHLENBQUMsQ0FBQztRQTZHNUI7O1dBRUc7UUFDSyx3QkFBbUIsR0FBRyxLQUFLLEVBQUUsSUFBbUIsRUFBRSxNQUFxQixFQUFFLEVBQUU7WUFDakYsSUFBSSxJQUFJLENBQUMsUUFBUSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sSUFBSSxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDeEQsT0FBTztZQUNULENBQUM7WUFFRCxPQUFPLENBQUMsS0FBSyxDQUNYLGdEQUFnRCxJQUFJLFlBQVksTUFBTSxLQUFLO2dCQUMzRSxXQUFXLElBQUksQ0FBQyxlQUFlLEdBQUcsQ0FBQyxJQUFJLG9CQUFvQixFQUFFLENBQzlELENBQUM7WUFFRixJQUFJLENBQUMsT0FBTyxHQUFHLEtBQUssQ0FBQztZQUVyQixJQUFJLElBQUksQ0FBQyxlQUFlLElBQUksb0JBQW9CLEVBQUUsQ0FBQztnQkFDakQsT0FBTyxDQUFDLEtBQUssQ0FB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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@serve.zone/remoteingress",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.",
|
|
6
6
|
"main": "dist_ts/index.js",
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@serve.zone/remoteingress',
|
|
6
|
-
version: '4.
|
|
6
|
+
version: '4.4.0',
|
|
7
7
|
description: 'Edge ingress tunnel for DcRouter - accepts incoming TCP connections at network edge and tunnels them to DcRouter SmartProxy preserving client IP via PROXY protocol v1.'
|
|
8
8
|
}
|
|
@@ -40,9 +40,16 @@ export interface IEdgeConfig {
|
|
|
40
40
|
secret: string;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
const MAX_RESTART_ATTEMPTS = 10;
|
|
44
|
+
const MAX_RESTART_BACKOFF_MS = 30_000;
|
|
45
|
+
|
|
43
46
|
export class RemoteIngressEdge extends EventEmitter {
|
|
44
47
|
private bridge: InstanceType<typeof plugins.smartrust.RustBridge<TEdgeCommands>>;
|
|
45
48
|
private started = false;
|
|
49
|
+
private stopping = false;
|
|
50
|
+
private savedConfig: IEdgeConfig | null = null;
|
|
51
|
+
private restartBackoffMs = 1000;
|
|
52
|
+
private restartAttempts = 0;
|
|
46
53
|
private statusInterval: ReturnType<typeof setInterval> | undefined;
|
|
47
54
|
|
|
48
55
|
constructor() {
|
|
@@ -109,11 +116,17 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
109
116
|
edgeConfig = config;
|
|
110
117
|
}
|
|
111
118
|
|
|
119
|
+
this.savedConfig = edgeConfig;
|
|
120
|
+
this.stopping = false;
|
|
121
|
+
|
|
112
122
|
const spawned = await this.bridge.spawn();
|
|
113
123
|
if (!spawned) {
|
|
114
124
|
throw new Error('Failed to spawn remoteingress-bin');
|
|
115
125
|
}
|
|
116
126
|
|
|
127
|
+
// Register crash recovery handler
|
|
128
|
+
this.bridge.on('exit', this.handleCrashRecovery);
|
|
129
|
+
|
|
117
130
|
await this.bridge.sendCommand('startEdge', {
|
|
118
131
|
hubHost: edgeConfig.hubHost,
|
|
119
132
|
hubPort: edgeConfig.hubPort ?? 8443,
|
|
@@ -122,6 +135,8 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
122
135
|
});
|
|
123
136
|
|
|
124
137
|
this.started = true;
|
|
138
|
+
this.restartAttempts = 0;
|
|
139
|
+
this.restartBackoffMs = 1000;
|
|
125
140
|
|
|
126
141
|
// Start periodic status logging
|
|
127
142
|
this.statusInterval = setInterval(async () => {
|
|
@@ -142,6 +157,7 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
142
157
|
* Stop the edge and kill the Rust process.
|
|
143
158
|
*/
|
|
144
159
|
public async stop(): Promise<void> {
|
|
160
|
+
this.stopping = true;
|
|
145
161
|
if (this.statusInterval) {
|
|
146
162
|
clearInterval(this.statusInterval);
|
|
147
163
|
this.statusInterval = undefined;
|
|
@@ -152,6 +168,7 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
152
168
|
} catch {
|
|
153
169
|
// Process may already be dead
|
|
154
170
|
}
|
|
171
|
+
this.bridge.removeListener('exit', this.handleCrashRecovery);
|
|
155
172
|
this.bridge.kill();
|
|
156
173
|
this.started = false;
|
|
157
174
|
}
|
|
@@ -170,4 +187,55 @@ export class RemoteIngressEdge extends EventEmitter {
|
|
|
170
187
|
public get running(): boolean {
|
|
171
188
|
return this.bridge.running;
|
|
172
189
|
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Handle unexpected Rust binary crash — auto-restart with backoff.
|
|
193
|
+
*/
|
|
194
|
+
private handleCrashRecovery = async (code: number | null, signal: string | null) => {
|
|
195
|
+
if (this.stopping || !this.started || !this.savedConfig) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.error(
|
|
200
|
+
`[RemoteIngressEdge] Rust binary crashed (code=${code}, signal=${signal}), ` +
|
|
201
|
+
`attempt ${this.restartAttempts + 1}/${MAX_RESTART_ATTEMPTS}`
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
this.started = false;
|
|
205
|
+
|
|
206
|
+
if (this.restartAttempts >= MAX_RESTART_ATTEMPTS) {
|
|
207
|
+
console.error('[RemoteIngressEdge] Max restart attempts reached, giving up');
|
|
208
|
+
this.emit('crashRecoveryFailed');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await new Promise(resolve => setTimeout(resolve, this.restartBackoffMs));
|
|
213
|
+
this.restartBackoffMs = Math.min(this.restartBackoffMs * 2, MAX_RESTART_BACKOFF_MS);
|
|
214
|
+
this.restartAttempts++;
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const spawned = await this.bridge.spawn();
|
|
218
|
+
if (!spawned) {
|
|
219
|
+
console.error('[RemoteIngressEdge] Failed to respawn binary');
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.bridge.on('exit', this.handleCrashRecovery);
|
|
224
|
+
|
|
225
|
+
await this.bridge.sendCommand('startEdge', {
|
|
226
|
+
hubHost: this.savedConfig.hubHost,
|
|
227
|
+
hubPort: this.savedConfig.hubPort ?? 8443,
|
|
228
|
+
edgeId: this.savedConfig.edgeId,
|
|
229
|
+
secret: this.savedConfig.secret,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
this.started = true;
|
|
233
|
+
this.restartAttempts = 0;
|
|
234
|
+
this.restartBackoffMs = 1000;
|
|
235
|
+
console.log('[RemoteIngressEdge] Successfully recovered from crash');
|
|
236
|
+
this.emit('crashRecovered');
|
|
237
|
+
} catch (err) {
|
|
238
|
+
console.error(`[RemoteIngressEdge] Crash recovery failed: ${err}`);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
173
241
|
}
|
|
@@ -50,9 +50,19 @@ export interface IHubConfig {
|
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
type TAllowedEdge = { id: string; secret: string; listenPorts?: number[]; stunIntervalSecs?: number };
|
|
54
|
+
|
|
55
|
+
const MAX_RESTART_ATTEMPTS = 10;
|
|
56
|
+
const MAX_RESTART_BACKOFF_MS = 30_000;
|
|
57
|
+
|
|
53
58
|
export class RemoteIngressHub extends EventEmitter {
|
|
54
59
|
private bridge: InstanceType<typeof plugins.smartrust.RustBridge<THubCommands>>;
|
|
55
60
|
private started = false;
|
|
61
|
+
private stopping = false;
|
|
62
|
+
private savedConfig: IHubConfig | null = null;
|
|
63
|
+
private savedEdges: TAllowedEdge[] = [];
|
|
64
|
+
private restartBackoffMs = 1000;
|
|
65
|
+
private restartAttempts = 0;
|
|
56
66
|
|
|
57
67
|
constructor() {
|
|
58
68
|
super();
|
|
@@ -98,11 +108,17 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
98
108
|
* Start the hub — spawns the Rust binary and starts the tunnel server.
|
|
99
109
|
*/
|
|
100
110
|
public async start(config: IHubConfig = {}): Promise<void> {
|
|
111
|
+
this.savedConfig = config;
|
|
112
|
+
this.stopping = false;
|
|
113
|
+
|
|
101
114
|
const spawned = await this.bridge.spawn();
|
|
102
115
|
if (!spawned) {
|
|
103
116
|
throw new Error('Failed to spawn remoteingress-bin');
|
|
104
117
|
}
|
|
105
118
|
|
|
119
|
+
// Register crash recovery handler
|
|
120
|
+
this.bridge.on('exit', this.handleCrashRecovery);
|
|
121
|
+
|
|
106
122
|
await this.bridge.sendCommand('startHub', {
|
|
107
123
|
tunnelPort: config.tunnelPort ?? 8443,
|
|
108
124
|
targetHost: config.targetHost ?? '127.0.0.1',
|
|
@@ -112,18 +128,22 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
112
128
|
});
|
|
113
129
|
|
|
114
130
|
this.started = true;
|
|
131
|
+
this.restartAttempts = 0;
|
|
132
|
+
this.restartBackoffMs = 1000;
|
|
115
133
|
}
|
|
116
134
|
|
|
117
135
|
/**
|
|
118
136
|
* Stop the hub and kill the Rust process.
|
|
119
137
|
*/
|
|
120
138
|
public async stop(): Promise<void> {
|
|
139
|
+
this.stopping = true;
|
|
121
140
|
if (this.started) {
|
|
122
141
|
try {
|
|
123
142
|
await this.bridge.sendCommand('stopHub', {} as Record<string, never>);
|
|
124
143
|
} catch {
|
|
125
144
|
// Process may already be dead
|
|
126
145
|
}
|
|
146
|
+
this.bridge.removeListener('exit', this.handleCrashRecovery);
|
|
127
147
|
this.bridge.kill();
|
|
128
148
|
this.started = false;
|
|
129
149
|
}
|
|
@@ -132,7 +152,8 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
132
152
|
/**
|
|
133
153
|
* Update the list of allowed edges that can connect to this hub.
|
|
134
154
|
*/
|
|
135
|
-
public async updateAllowedEdges(edges:
|
|
155
|
+
public async updateAllowedEdges(edges: TAllowedEdge[]): Promise<void> {
|
|
156
|
+
this.savedEdges = edges;
|
|
136
157
|
await this.bridge.sendCommand('updateAllowedEdges', { edges });
|
|
137
158
|
}
|
|
138
159
|
|
|
@@ -149,4 +170,62 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
149
170
|
public get running(): boolean {
|
|
150
171
|
return this.bridge.running;
|
|
151
172
|
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Handle unexpected Rust binary crash — auto-restart with backoff.
|
|
176
|
+
*/
|
|
177
|
+
private handleCrashRecovery = async (code: number | null, signal: string | null) => {
|
|
178
|
+
if (this.stopping || !this.started || !this.savedConfig) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.error(
|
|
183
|
+
`[RemoteIngressHub] Rust binary crashed (code=${code}, signal=${signal}), ` +
|
|
184
|
+
`attempt ${this.restartAttempts + 1}/${MAX_RESTART_ATTEMPTS}`
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
this.started = false;
|
|
188
|
+
|
|
189
|
+
if (this.restartAttempts >= MAX_RESTART_ATTEMPTS) {
|
|
190
|
+
console.error('[RemoteIngressHub] Max restart attempts reached, giving up');
|
|
191
|
+
this.emit('crashRecoveryFailed');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await new Promise(resolve => setTimeout(resolve, this.restartBackoffMs));
|
|
196
|
+
this.restartBackoffMs = Math.min(this.restartBackoffMs * 2, MAX_RESTART_BACKOFF_MS);
|
|
197
|
+
this.restartAttempts++;
|
|
198
|
+
|
|
199
|
+
try {
|
|
200
|
+
const spawned = await this.bridge.spawn();
|
|
201
|
+
if (!spawned) {
|
|
202
|
+
console.error('[RemoteIngressHub] Failed to respawn binary');
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
this.bridge.on('exit', this.handleCrashRecovery);
|
|
207
|
+
|
|
208
|
+
const config = this.savedConfig;
|
|
209
|
+
await this.bridge.sendCommand('startHub', {
|
|
210
|
+
tunnelPort: config.tunnelPort ?? 8443,
|
|
211
|
+
targetHost: config.targetHost ?? '127.0.0.1',
|
|
212
|
+
...(config.tls?.certPem && config.tls?.keyPem
|
|
213
|
+
? { tlsCertPem: config.tls.certPem, tlsKeyPem: config.tls.keyPem }
|
|
214
|
+
: {}),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Restore allowed edges
|
|
218
|
+
if (this.savedEdges.length > 0) {
|
|
219
|
+
await this.bridge.sendCommand('updateAllowedEdges', { edges: this.savedEdges });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
this.started = true;
|
|
223
|
+
this.restartAttempts = 0;
|
|
224
|
+
this.restartBackoffMs = 1000;
|
|
225
|
+
console.log('[RemoteIngressHub] Successfully recovered from crash');
|
|
226
|
+
this.emit('crashRecovered');
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.error(`[RemoteIngressHub] Crash recovery failed: ${err}`);
|
|
229
|
+
}
|
|
230
|
+
};
|
|
152
231
|
}
|