@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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/remoteingress',
6
- version: '4.2.0',
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzZWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2NsYXNzZXMucmVtb3RlaW5ncmVzc2VkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLFFBQVEsQ0FBQztBQUN0QyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQXdDM0QsTUFBTSxPQUFPLGlCQUFrQixTQUFRLFlBQVk7SUFLakQ7UUFDRSxLQUFLLEVBQUUsQ0FBQztRQUpGLFlBQU8sR0FBRyxLQUFLLENBQUM7UUFNdEIsTUFBTSxVQUFVLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQ3JDLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksR0FBRyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQ3ZELElBQUksQ0FDTCxDQUFDO1FBRUYsSUFBSSxDQUFDLE1BQU0sR0FBRyxJQUFJLE9BQU8sQ0FBQyxTQUFTLENBQUMsVUFBVSxDQUFnQjtZQUM1RCxVQUFVLEVBQUUsbUJBQW1CO1lBQy9CLE9BQU8sRUFBRSxDQUFDLGNBQWMsQ0FBQztZQUN6QixnQkFBZ0IsRUFBRSxNQUFNO1lBQ3hCLGNBQWMsRUFBRSxNQUFNO1lBQ3RCLFVBQVUsRUFBRTtnQkFDVixxREFBcUQ7Z0JBQ3JELE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxXQUFXLEVBQUUscUJBQXFCLE9BQU8sQ0FBQyxRQUFRLEtBQUssT0FBTyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLE9BQU8sSUFBSSxPQUFPLENBQUMsSUFBSSxLQUFLLEtBQUssQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMs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
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: Array<{
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5yZW1vdGVpbmdyZXNzaHViLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5yZW1vdGVpbmdyZXNzaHViLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxRQUFRLENBQUM7QUE2Q3RDLE1BQU0sT0FBTyxnQkFBaUIsU0FBUSxZQUFZO0lBSWhEO1FBQ0UsS0FBSyxFQUFFLENBQUM7UUFIRixZQUFPLEdBQUcsS0FBSyxDQUFDO1FBS3RCLE1BQU0sVUFBVSxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUNyQyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEdBQUcsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxFQUN2RCxJQUFJLENBQ0wsQ0FBQztRQUVGLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxPQUFPLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBZTtZQUMzRCxVQUFVLEVBQUUsbUJBQW1CO1lBQy9CLE9BQU8sRUFBRSxDQUFDLGNBQWMsQ0FBQztZQUN6QixnQkFBZ0IsRUFBRSxNQUFNO1lBQ3hCLGNBQWMsRUFBRSxNQUFNO1lBQ3RCLFVBQVUsRUFBRTtnQkFDVixxREFBcUQ7Z0JBQ3JELE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxXQUFXLEVBQUUscUJBQXFCLE9BQU8sQ0FBQyxRQUFRLEtBQUssT0FBTyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLE9BQU8sSUFBSSxPQUFPLENBQUMsSUFBSSxLQUFLLEtBQUssQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQ3hLLHlDQUF5QztnQkFDekMsT0FBTyxDQUFDLElB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
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.2.0",
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",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/remoteingress',
6
- version: '4.2.0',
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: Array<{ id: string; secret: string; listenPorts?: number[]; stunIntervalSecs?: number }>): Promise<void> {
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
  }