@serve.zone/remoteingress 4.2.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 +21 -6
- package/dist_ts/classes.remoteingresshub.js +67 -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 +89 -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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
|
|
@@ -2,10 +2,25 @@ import { EventEmitter } from 'events';
|
|
|
2
2
|
export interface IHubConfig {
|
|
3
3
|
tunnelPort?: number;
|
|
4
4
|
targetHost?: string;
|
|
5
|
+
tls?: {
|
|
6
|
+
certPem?: string;
|
|
7
|
+
keyPem?: string;
|
|
8
|
+
};
|
|
5
9
|
}
|
|
10
|
+
type TAllowedEdge = {
|
|
11
|
+
id: string;
|
|
12
|
+
secret: string;
|
|
13
|
+
listenPorts?: number[];
|
|
14
|
+
stunIntervalSecs?: number;
|
|
15
|
+
};
|
|
6
16
|
export declare class RemoteIngressHub extends EventEmitter {
|
|
7
17
|
private bridge;
|
|
8
18
|
private started;
|
|
19
|
+
private stopping;
|
|
20
|
+
private savedConfig;
|
|
21
|
+
private savedEdges;
|
|
22
|
+
private restartBackoffMs;
|
|
23
|
+
private restartAttempts;
|
|
9
24
|
constructor();
|
|
10
25
|
/**
|
|
11
26
|
* Start the hub — spawns the Rust binary and starts the tunnel server.
|
|
@@ -18,12 +33,7 @@ export declare class RemoteIngressHub extends EventEmitter {
|
|
|
18
33
|
/**
|
|
19
34
|
* Update the list of allowed edges that can connect to this hub.
|
|
20
35
|
*/
|
|
21
|
-
updateAllowedEdges(edges:
|
|
22
|
-
id: string;
|
|
23
|
-
secret: string;
|
|
24
|
-
listenPorts?: number[];
|
|
25
|
-
stunIntervalSecs?: number;
|
|
26
|
-
}>): Promise<void>;
|
|
36
|
+
updateAllowedEdges(edges: TAllowedEdge[]): Promise<void>;
|
|
27
37
|
/**
|
|
28
38
|
* Get the current hub status.
|
|
29
39
|
*/
|
|
@@ -41,4 +51,9 @@ export declare class RemoteIngressHub extends EventEmitter {
|
|
|
41
51
|
* Check if the bridge is running.
|
|
42
52
|
*/
|
|
43
53
|
get running(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Handle unexpected Rust binary crash — auto-restart with backoff.
|
|
56
|
+
*/
|
|
57
|
+
private handleCrashRecovery;
|
|
44
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,20 +93,30 @@ 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',
|
|
107
|
+
...(config.tls?.certPem && config.tls?.keyPem
|
|
108
|
+
? { tlsCertPem: config.tls.certPem, tlsKeyPem: config.tls.keyPem }
|
|
109
|
+
: {}),
|
|
49
110
|
});
|
|
50
111
|
this.started = true;
|
|
112
|
+
this.restartAttempts = 0;
|
|
113
|
+
this.restartBackoffMs = 1000;
|
|
51
114
|
}
|
|
52
115
|
/**
|
|
53
116
|
* Stop the hub and kill the Rust process.
|
|
54
117
|
*/
|
|
55
118
|
async stop() {
|
|
119
|
+
this.stopping = true;
|
|
56
120
|
if (this.started) {
|
|
57
121
|
try {
|
|
58
122
|
await this.bridge.sendCommand('stopHub', {});
|
|
@@ -60,6 +124,7 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
60
124
|
catch {
|
|
61
125
|
// Process may already be dead
|
|
62
126
|
}
|
|
127
|
+
this.bridge.removeListener('exit', this.handleCrashRecovery);
|
|
63
128
|
this.bridge.kill();
|
|
64
129
|
this.started = false;
|
|
65
130
|
}
|
|
@@ -68,6 +133,7 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
68
133
|
* Update the list of allowed edges that can connect to this hub.
|
|
69
134
|
*/
|
|
70
135
|
async updateAllowedEdges(edges) {
|
|
136
|
+
this.savedEdges = edges;
|
|
71
137
|
await this.bridge.sendCommand('updateAllowedEdges', { edges });
|
|
72
138
|
}
|
|
73
139
|
/**
|
|
@@ -83,4 +149,4 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
83
149
|
return this.bridge.running;
|
|
84
150
|
}
|
|
85
151
|
}
|
|
86
|
-
//# 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
|
}
|
|
@@ -11,6 +11,8 @@ type THubCommands = {
|
|
|
11
11
|
params: {
|
|
12
12
|
tunnelPort: number;
|
|
13
13
|
targetHost?: string;
|
|
14
|
+
tlsCertPem?: string;
|
|
15
|
+
tlsKeyPem?: string;
|
|
14
16
|
};
|
|
15
17
|
result: { started: boolean };
|
|
16
18
|
};
|
|
@@ -42,11 +44,25 @@ type THubCommands = {
|
|
|
42
44
|
export interface IHubConfig {
|
|
43
45
|
tunnelPort?: number;
|
|
44
46
|
targetHost?: string;
|
|
47
|
+
tls?: {
|
|
48
|
+
certPem?: string;
|
|
49
|
+
keyPem?: string;
|
|
50
|
+
};
|
|
45
51
|
}
|
|
46
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
|
+
|
|
47
58
|
export class RemoteIngressHub extends EventEmitter {
|
|
48
59
|
private bridge: InstanceType<typeof plugins.smartrust.RustBridge<THubCommands>>;
|
|
49
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;
|
|
50
66
|
|
|
51
67
|
constructor() {
|
|
52
68
|
super();
|
|
@@ -92,29 +108,42 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
92
108
|
* Start the hub — spawns the Rust binary and starts the tunnel server.
|
|
93
109
|
*/
|
|
94
110
|
public async start(config: IHubConfig = {}): Promise<void> {
|
|
111
|
+
this.savedConfig = config;
|
|
112
|
+
this.stopping = false;
|
|
113
|
+
|
|
95
114
|
const spawned = await this.bridge.spawn();
|
|
96
115
|
if (!spawned) {
|
|
97
116
|
throw new Error('Failed to spawn remoteingress-bin');
|
|
98
117
|
}
|
|
99
118
|
|
|
119
|
+
// Register crash recovery handler
|
|
120
|
+
this.bridge.on('exit', this.handleCrashRecovery);
|
|
121
|
+
|
|
100
122
|
await this.bridge.sendCommand('startHub', {
|
|
101
123
|
tunnelPort: config.tunnelPort ?? 8443,
|
|
102
124
|
targetHost: config.targetHost ?? '127.0.0.1',
|
|
125
|
+
...(config.tls?.certPem && config.tls?.keyPem
|
|
126
|
+
? { tlsCertPem: config.tls.certPem, tlsKeyPem: config.tls.keyPem }
|
|
127
|
+
: {}),
|
|
103
128
|
});
|
|
104
129
|
|
|
105
130
|
this.started = true;
|
|
131
|
+
this.restartAttempts = 0;
|
|
132
|
+
this.restartBackoffMs = 1000;
|
|
106
133
|
}
|
|
107
134
|
|
|
108
135
|
/**
|
|
109
136
|
* Stop the hub and kill the Rust process.
|
|
110
137
|
*/
|
|
111
138
|
public async stop(): Promise<void> {
|
|
139
|
+
this.stopping = true;
|
|
112
140
|
if (this.started) {
|
|
113
141
|
try {
|
|
114
142
|
await this.bridge.sendCommand('stopHub', {} as Record<string, never>);
|
|
115
143
|
} catch {
|
|
116
144
|
// Process may already be dead
|
|
117
145
|
}
|
|
146
|
+
this.bridge.removeListener('exit', this.handleCrashRecovery);
|
|
118
147
|
this.bridge.kill();
|
|
119
148
|
this.started = false;
|
|
120
149
|
}
|
|
@@ -123,7 +152,8 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
123
152
|
/**
|
|
124
153
|
* Update the list of allowed edges that can connect to this hub.
|
|
125
154
|
*/
|
|
126
|
-
public async updateAllowedEdges(edges:
|
|
155
|
+
public async updateAllowedEdges(edges: TAllowedEdge[]): Promise<void> {
|
|
156
|
+
this.savedEdges = edges;
|
|
127
157
|
await this.bridge.sendCommand('updateAllowedEdges', { edges });
|
|
128
158
|
}
|
|
129
159
|
|
|
@@ -140,4 +170,62 @@ export class RemoteIngressHub extends EventEmitter {
|
|
|
140
170
|
public get running(): boolean {
|
|
141
171
|
return this.bridge.running;
|
|
142
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
|
+
};
|
|
143
231
|
}
|