rds_ssm_connect 1.7.14 → 1.7.15
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/connect.js +53 -18
- package/gui-adapter.js +12 -15
- package/package.json +1 -1
- package/src/App.svelte +10 -3
- package/src-tauri/Cargo.lock +1 -1
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/lib.rs +14 -1
- package/src-tauri/tauri.conf.json +1 -1
package/connect.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { exec } from 'node:child_process'
|
|
3
|
+
import { exec, spawn } from 'node:child_process'
|
|
4
4
|
import { EventEmitter } from 'node:events'
|
|
5
5
|
import fs from 'node:fs/promises'
|
|
6
6
|
import net from 'node:net'
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from './configLoader.js'
|
|
15
15
|
|
|
16
16
|
// Package info for version checking
|
|
17
|
-
const packageJson = { name: 'rds_ssm_connect', version: '1.7.
|
|
17
|
+
const packageJson = { name: 'rds_ssm_connect', version: '1.7.15' }
|
|
18
18
|
|
|
19
19
|
const execAsync = promisify(exec)
|
|
20
20
|
|
|
@@ -83,20 +83,30 @@ async function checkForUpdates() {
|
|
|
83
83
|
// Store active child processes for cleanup
|
|
84
84
|
let activeChildProcesses = []
|
|
85
85
|
|
|
86
|
+
// Kill entire process group (shell + aws-vault + session-manager-plugin).
|
|
87
|
+
// Requires the child to have been spawned with `detached: true` so that
|
|
88
|
+
// setsid() makes it a process group leader.
|
|
89
|
+
function killProcessTree(child) {
|
|
90
|
+
if (!child || !child.pid) return
|
|
91
|
+
try {
|
|
92
|
+
process.kill(-child.pid, 'SIGTERM') // negative PID = kill entire process group
|
|
93
|
+
} catch (_err) {
|
|
94
|
+
// ESRCH: process group already exited — safe to ignore
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
86
98
|
// Handle graceful shutdown
|
|
87
99
|
function setupProcessCleanup() {
|
|
88
|
-
const
|
|
100
|
+
const killAll = () => {
|
|
89
101
|
activeChildProcesses.forEach((child) => {
|
|
90
|
-
|
|
91
|
-
child.kill('SIGTERM')
|
|
92
|
-
}
|
|
102
|
+
killProcessTree(child)
|
|
93
103
|
})
|
|
94
|
-
|
|
104
|
+
activeChildProcesses = []
|
|
95
105
|
}
|
|
96
106
|
|
|
97
|
-
process.on('SIGINT',
|
|
98
|
-
process.on('SIGTERM',
|
|
99
|
-
process.on('exit',
|
|
107
|
+
process.on('SIGINT', () => { killAll(); process.exit(0) })
|
|
108
|
+
process.on('SIGTERM', () => { killAll(); process.exit(0) })
|
|
109
|
+
process.on('exit', killAll)
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
async function readAwsConfig() {
|
|
@@ -251,6 +261,7 @@ async function handleTargetNotConnectedError(
|
|
|
251
261
|
region,
|
|
252
262
|
retryCount,
|
|
253
263
|
maxRetries,
|
|
264
|
+
onChild,
|
|
254
265
|
) {
|
|
255
266
|
// Terminate the disconnected instance
|
|
256
267
|
await terminateBastionInstance(ENV, instanceId, region)
|
|
@@ -270,6 +281,7 @@ async function handleTargetNotConnectedError(
|
|
|
270
281
|
region,
|
|
271
282
|
retryCount + 1,
|
|
272
283
|
maxRetries,
|
|
284
|
+
onChild,
|
|
273
285
|
)
|
|
274
286
|
}
|
|
275
287
|
|
|
@@ -282,7 +294,11 @@ function executePortForwardingCommand(
|
|
|
282
294
|
region,
|
|
283
295
|
) {
|
|
284
296
|
const portForwardingCommand = `aws-vault exec ${ENV} -- aws ssm start-session --region ${region} --target ${instanceId} --document-name AWS-StartPortForwardingSessionToRemoteHost --parameters "host=${rdsEndpoint},portNumber='${remotePort}',localPortNumber='${portNumber}'" --cli-connect-timeout 0`
|
|
285
|
-
const child =
|
|
297
|
+
const child = spawn(portForwardingCommand, {
|
|
298
|
+
shell: true,
|
|
299
|
+
detached: true,
|
|
300
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
301
|
+
})
|
|
286
302
|
|
|
287
303
|
// Register child process for cleanup
|
|
288
304
|
activeChildProcesses.push(child)
|
|
@@ -299,6 +315,7 @@ async function startPortForwardingWithConfig(
|
|
|
299
315
|
region,
|
|
300
316
|
retryCount = 0,
|
|
301
317
|
maxRetries = RETRY_CONFIG.PORT_FORWARDING_MAX_RETRIES,
|
|
318
|
+
onChild,
|
|
302
319
|
) {
|
|
303
320
|
return new Promise((resolve, reject) => {
|
|
304
321
|
const child = executePortForwardingCommand(
|
|
@@ -309,6 +326,11 @@ async function startPortForwardingWithConfig(
|
|
|
309
326
|
remotePort,
|
|
310
327
|
region,
|
|
311
328
|
)
|
|
329
|
+
// Notify caller of the new child (used for per-connection tracking).
|
|
330
|
+
// If the connection was already disconnected, the callback kills this
|
|
331
|
+
// child immediately so it doesn't linger as an orphan.
|
|
332
|
+
if (onChild) onChild(child)
|
|
333
|
+
|
|
312
334
|
const sessionState = monitorPortForwardingSession(child)
|
|
313
335
|
|
|
314
336
|
child.on('close', async (code) => {
|
|
@@ -331,6 +353,7 @@ async function startPortForwardingWithConfig(
|
|
|
331
353
|
region,
|
|
332
354
|
retryCount,
|
|
333
355
|
maxRetries,
|
|
356
|
+
onChild,
|
|
334
357
|
)
|
|
335
358
|
resolve()
|
|
336
359
|
} else if (code !== 0) {
|
|
@@ -543,6 +566,18 @@ async function connect(projectKey, profile, options = {}) {
|
|
|
543
566
|
|
|
544
567
|
let manualDisconnect = false
|
|
545
568
|
let stopKeepalive = null
|
|
569
|
+
let currentChild = null // per-connection child tracking
|
|
570
|
+
|
|
571
|
+
// Called whenever a new child process is spawned for this connection.
|
|
572
|
+
// If disconnect() was already called, kills the child immediately so
|
|
573
|
+
// it doesn't linger as an orphan during retry chains.
|
|
574
|
+
const onChild = (child) => {
|
|
575
|
+
currentChild = child
|
|
576
|
+
if (manualDisconnect) {
|
|
577
|
+
killProcessTree(child)
|
|
578
|
+
activeChildProcesses = activeChildProcesses.filter((p) => p !== child)
|
|
579
|
+
}
|
|
580
|
+
}
|
|
546
581
|
|
|
547
582
|
// Emit status updates
|
|
548
583
|
const emit = (event, data) => {
|
|
@@ -600,6 +635,7 @@ async function connect(projectKey, profile, options = {}) {
|
|
|
600
635
|
region,
|
|
601
636
|
0,
|
|
602
637
|
RETRY_CONFIG.PORT_FORWARDING_MAX_RETRIES,
|
|
638
|
+
onChild,
|
|
603
639
|
)
|
|
604
640
|
|
|
605
641
|
// Session ended — clean up keepalive
|
|
@@ -672,13 +708,12 @@ async function connect(projectKey, profile, options = {}) {
|
|
|
672
708
|
disconnect: () => {
|
|
673
709
|
manualDisconnect = true
|
|
674
710
|
stopKeepalive?.()
|
|
675
|
-
// Kill
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
}
|
|
681
|
-
activeChildProcesses = []
|
|
711
|
+
// Kill only THIS connection's child process group
|
|
712
|
+
if (currentChild) {
|
|
713
|
+
killProcessTree(currentChild)
|
|
714
|
+
activeChildProcesses = activeChildProcesses.filter((p) => p !== currentChild)
|
|
715
|
+
currentChild = null
|
|
716
|
+
}
|
|
682
717
|
},
|
|
683
718
|
waitForClose: () => portForwardingPromise,
|
|
684
719
|
}
|
package/gui-adapter.js
CHANGED
|
@@ -253,19 +253,19 @@ async function handleCommand(command) {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
//
|
|
257
|
-
function
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
connection.disconnect()
|
|
261
|
-
}
|
|
262
|
-
activeConnections.clear()
|
|
263
|
-
process.exit(0)
|
|
256
|
+
// Disconnect all active connections (idempotent — safe to call multiple times)
|
|
257
|
+
function disconnectAll() {
|
|
258
|
+
for (const [_connId, connection] of activeConnections) {
|
|
259
|
+
connection.disconnect()
|
|
264
260
|
}
|
|
261
|
+
activeConnections.clear()
|
|
262
|
+
}
|
|
265
263
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
process.on('
|
|
264
|
+
// Handle process signals for cleanup
|
|
265
|
+
function setupCleanup() {
|
|
266
|
+
process.on('SIGINT', () => { disconnectAll(); process.exit(0) })
|
|
267
|
+
process.on('SIGTERM', () => { disconnectAll(); process.exit(0) })
|
|
268
|
+
process.on('exit', disconnectAll)
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
// Main entry point
|
|
@@ -294,10 +294,7 @@ async function main() {
|
|
|
294
294
|
})
|
|
295
295
|
|
|
296
296
|
rl.on('close', () => {
|
|
297
|
-
|
|
298
|
-
connection.disconnect()
|
|
299
|
-
}
|
|
300
|
-
activeConnections.clear()
|
|
297
|
+
disconnectAll()
|
|
301
298
|
process.exit(0)
|
|
302
299
|
})
|
|
303
300
|
}
|
package/package.json
CHANGED
package/src/App.svelte
CHANGED
|
@@ -182,7 +182,12 @@ async function confirmClose() {
|
|
|
182
182
|
} catch (_err) {
|
|
183
183
|
// Best-effort disconnect before closing
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
try {
|
|
186
|
+
await invoke('quit_app')
|
|
187
|
+
} catch (_err) {
|
|
188
|
+
// If quit_app fails, force close via window API
|
|
189
|
+
appWindow?.close()
|
|
190
|
+
}
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
function cancelClose() {
|
|
@@ -428,8 +433,10 @@ async function handleInstallUpdate() {
|
|
|
428
433
|
|
|
429
434
|
try {
|
|
430
435
|
await invoke('install_update')
|
|
431
|
-
|
|
432
|
-
|
|
436
|
+
// App should auto-restart and never reach here, but just in case:
|
|
437
|
+
isUpdating = false
|
|
438
|
+
showUpdateBanner = false
|
|
439
|
+
statusMessage = 'Update installed successfully.'
|
|
433
440
|
} catch (err) {
|
|
434
441
|
errorMessage = `Update failed: ${err}`
|
|
435
442
|
isUpdating = false
|
package/src-tauri/Cargo.lock
CHANGED
package/src-tauri/Cargo.toml
CHANGED
package/src-tauri/src/lib.rs
CHANGED
|
@@ -818,6 +818,8 @@ async fn install_update(app_handle: AppHandle) -> Result<(), String> {
|
|
|
818
818
|
.await
|
|
819
819
|
.map_err(|e| format!("Failed to download/install update: {}", e))?;
|
|
820
820
|
|
|
821
|
+
app_handle.restart();
|
|
822
|
+
|
|
821
823
|
Ok(())
|
|
822
824
|
}
|
|
823
825
|
|
|
@@ -836,7 +838,18 @@ async fn open_url(app_handle: AppHandle, url: String) -> Result<(), String> {
|
|
|
836
838
|
}
|
|
837
839
|
|
|
838
840
|
#[tauri::command]
|
|
839
|
-
async fn quit_app(
|
|
841
|
+
async fn quit_app(
|
|
842
|
+
app_handle: AppHandle,
|
|
843
|
+
state: tauri::State<'_, Arc<TokioMutex<SidecarState>>>,
|
|
844
|
+
) -> Result<(), String> {
|
|
845
|
+
// Kill the sidecar process (triggers its cleanup handlers which
|
|
846
|
+
// disconnect all active connections and kill process groups)
|
|
847
|
+
{
|
|
848
|
+
let mut guard = state.lock().await;
|
|
849
|
+
if let Some(child) = guard.child.take() {
|
|
850
|
+
let _ = child.kill();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
840
853
|
app_handle.exit(0);
|
|
841
854
|
Ok(())
|
|
842
855
|
}
|