@miraj181/ipingyou 2.1.5 → 2.1.6
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/package.json +1 -1
- package/src/lib/cleanup.js +10 -2
- package/src/lib/tmux.js +10 -0
- package/src/modes/client.js +4 -3
- package/src/modes/host.js +59 -6
package/package.json
CHANGED
package/src/lib/cleanup.js
CHANGED
|
@@ -14,7 +14,7 @@ import fs from 'node:fs';
|
|
|
14
14
|
import os from 'node:os';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { execaCommand } from 'execa';
|
|
17
|
-
import { TMUX_SESSION_NAME, tmuxSocketArgs } from './tmux.js';
|
|
17
|
+
import { TMUX_SESSION_NAME, TMUX_SESSION_PREFIX, tmuxSocketArgs } from './tmux.js';
|
|
18
18
|
|
|
19
19
|
/** @type {Set<number>} — Active child PIDs we manage */
|
|
20
20
|
const trackedPIDs = new Set();
|
|
@@ -192,7 +192,15 @@ export async function executePanicMode() {
|
|
|
192
192
|
} else {
|
|
193
193
|
await execaCommand('pkill -9 -f cloudflared', { reject: false });
|
|
194
194
|
await execaCommand('pkill -9 -f "sshd:.*@"', { reject: false });
|
|
195
|
-
await execaCommand(`tmux ${tmuxSocketArgs().join(' ')} kill-
|
|
195
|
+
await execaCommand(`tmux ${tmuxSocketArgs().join(' ')} kill-server`, { reject: false });
|
|
196
|
+
const { stdout } = await execaCommand('tmux list-sessions -F "#{session_name}"', { reject: false });
|
|
197
|
+
const legacyNames = stdout
|
|
198
|
+
.split(/\r?\n/)
|
|
199
|
+
.filter(Boolean)
|
|
200
|
+
.filter(name => name === TMUX_SESSION_NAME || name.startsWith(TMUX_SESSION_PREFIX));
|
|
201
|
+
for (const name of legacyNames) {
|
|
202
|
+
await execaCommand(`tmux kill-session -t ${name}`, { reject: false });
|
|
203
|
+
}
|
|
196
204
|
}
|
|
197
205
|
} catch {}
|
|
198
206
|
|
package/src/lib/tmux.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
|
+
export const TMUX_SESSION_PREFIX = 'SecureLink_';
|
|
4
5
|
export const TMUX_SESSION_NAME = 'SecureLink_Session';
|
|
5
6
|
export const TMUX_SOCKET_PATH = path.join(os.tmpdir(), 'ipingyou-tmux.sock');
|
|
6
7
|
|
|
@@ -11,3 +12,12 @@ export function tmuxSocketArgs() {
|
|
|
11
12
|
export function tmuxSocketCommand() {
|
|
12
13
|
return `tmux -S ${TMUX_SOCKET_PATH}`;
|
|
13
14
|
}
|
|
15
|
+
|
|
16
|
+
export function buildTmuxSessionName(label) {
|
|
17
|
+
const safeLabel = String(label || 'client')
|
|
18
|
+
.replace(/[^a-zA-Z0-9_-]/g, '')
|
|
19
|
+
.slice(0, 24) || 'client';
|
|
20
|
+
const stamp = Date.now().toString(36);
|
|
21
|
+
const rand = Math.random().toString(36).slice(2, 6);
|
|
22
|
+
return `${TMUX_SESSION_PREFIX}${safeLabel}_${stamp}${rand}`;
|
|
23
|
+
}
|
package/src/modes/client.js
CHANGED
|
@@ -25,7 +25,7 @@ import { pushTelemetry, requestHostApproval, resolveUID, revokeUID, waitForAppro
|
|
|
25
25
|
import { calculateChecksum } from '../lib/checksum.js';
|
|
26
26
|
import { promptLocalPath, promptRemotePath } from '../lib/path-browser.js';
|
|
27
27
|
import { buildSshArgs, extractHostname, formatScpRemotePath, getKnownHostsOptions, getSshControlOptions, quoteRemoteShell } from '../lib/ssh.js';
|
|
28
|
-
import {
|
|
28
|
+
import { buildTmuxSessionName, tmuxSocketCommand } from '../lib/tmux.js';
|
|
29
29
|
import open from 'open';
|
|
30
30
|
import { cleanupSessionLog, initSessionLog, logSessionEvent, recordEvent } from '../lib/session-log.js';
|
|
31
31
|
|
|
@@ -88,8 +88,9 @@ async function connectSSH(username, hostname, privateKeyPath, persistKnownHosts
|
|
|
88
88
|
], { persistKnownHosts });
|
|
89
89
|
|
|
90
90
|
sshArgs.push(`${username}@${hostname}`);
|
|
91
|
-
const
|
|
92
|
-
const
|
|
91
|
+
const tmuxSession = buildTmuxSessionName(username);
|
|
92
|
+
const tmuxPrepare = `${tmuxSocketCommand()} has-session -t ${tmuxSession} 2>/dev/null || ${tmuxSocketCommand()} new-session -d -s ${tmuxSession}`;
|
|
93
|
+
const tmuxAttach = `${tmuxSocketCommand()} attach -t ${tmuxSession}`;
|
|
93
94
|
sshArgs.push('-t', `${tmuxPrepare} && ${tmuxAttach} || exec $SHELL -l`);
|
|
94
95
|
|
|
95
96
|
const child = execa('ssh', sshArgs, {
|
package/src/modes/host.js
CHANGED
|
@@ -29,7 +29,7 @@ import { startChatServer, openLocalChatUI } from '../lib/chat.js';
|
|
|
29
29
|
import { spawnTunnelSupervised } from '../lib/tunnel.js';
|
|
30
30
|
import { decideApprovalRequest, fetchApprovalRequests, pingBroker, registerWithBroker, revokeUID } from '../lib/broker.js';
|
|
31
31
|
import { cleanupSessionLog, getSessionLogPath, initSessionLog, logSessionEvent, recordEvent } from '../lib/session-log.js';
|
|
32
|
-
import { TMUX_SESSION_NAME, tmuxSocketArgs } from '../lib/tmux.js';
|
|
32
|
+
import { TMUX_SESSION_NAME, TMUX_SESSION_PREFIX, tmuxSocketArgs } from '../lib/tmux.js';
|
|
33
33
|
|
|
34
34
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
35
35
|
let BROKER_URL = process.env.BROKER_URL || 'https://ipingyou.onrender.com';
|
|
@@ -139,6 +139,37 @@ async function ensureTmuxInstalled() {
|
|
|
139
139
|
throw new Error('Homebrew is required to install tmux on macOS');
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
+
|
|
143
|
+
function isSecureLinkSession(name) {
|
|
144
|
+
return name === TMUX_SESSION_NAME || name.startsWith(TMUX_SESSION_PREFIX);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function listTmuxSessions(socketArgs = []) {
|
|
148
|
+
const result = await execa('tmux', [...socketArgs, 'list-sessions', '-F', '#{session_name}|#{session_created}'], { reject: false });
|
|
149
|
+
if (result.exitCode !== 0) return [];
|
|
150
|
+
return result.stdout
|
|
151
|
+
.split(/\r?\n/)
|
|
152
|
+
.filter(Boolean)
|
|
153
|
+
.map(line => {
|
|
154
|
+
const [name, createdAt] = line.split('|');
|
|
155
|
+
return { name, createdAt: Number(createdAt) || null };
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function getMirrorableSessions() {
|
|
160
|
+
const sessions = [];
|
|
161
|
+
const customSessions = await listTmuxSessions(tmuxSocketArgs());
|
|
162
|
+
customSessions
|
|
163
|
+
.filter(s => isSecureLinkSession(s.name))
|
|
164
|
+
.forEach(s => sessions.push({ ...s, socketArgs: tmuxSocketArgs(), source: 'custom' }));
|
|
165
|
+
|
|
166
|
+
const legacySessions = await listTmuxSessions();
|
|
167
|
+
legacySessions
|
|
168
|
+
.filter(s => isSecureLinkSession(s.name))
|
|
169
|
+
.forEach(s => sessions.push({ ...s, socketArgs: [], source: 'legacy' }));
|
|
170
|
+
|
|
171
|
+
return sessions;
|
|
172
|
+
}
|
|
142
173
|
}
|
|
143
174
|
} catch (err) {
|
|
144
175
|
spinner.fail(`tmux check/install failed: ${err.message}`);
|
|
@@ -811,16 +842,32 @@ async function hostDashboard(uid, password, serviceConfig, tunnelProcess, sessio
|
|
|
811
842
|
|
|
812
843
|
try {
|
|
813
844
|
await execaCommand('tmux -V', { reject: true });
|
|
814
|
-
const
|
|
815
|
-
if (
|
|
845
|
+
const sessions = await getMirrorableSessions();
|
|
846
|
+
if (sessions.length === 0) {
|
|
816
847
|
console.log(chalk.yellow(' ⚠️ No mirrored terminal session is active yet.'));
|
|
817
848
|
console.log(chalk.dim(' A client must choose "Connect via SSH" first. SCP-only clients do not create a tmux session.'));
|
|
818
849
|
console.log(chalk.dim(' tmux is needed on the host machine only; the client does not need tmux.'));
|
|
819
850
|
logSessionEvent('host_mirror_missing_session', {}, 'warn');
|
|
820
851
|
return waitForAction();
|
|
821
852
|
}
|
|
822
|
-
|
|
823
|
-
|
|
853
|
+
|
|
854
|
+
let target = sessions[0];
|
|
855
|
+
if (sessions.length > 1) {
|
|
856
|
+
const { sessionChoice } = await inquirer.prompt([{
|
|
857
|
+
type: 'list',
|
|
858
|
+
name: 'sessionChoice',
|
|
859
|
+
message: 'Select an active client session to mirror:',
|
|
860
|
+
choices: sessions.map((s, idx) => {
|
|
861
|
+
const created = s.createdAt ? new Date(s.createdAt * 1000).toLocaleTimeString() : 'Unknown';
|
|
862
|
+
const label = `${s.name} ${chalk.dim(`(started ${created})`)}`;
|
|
863
|
+
return { name: label, value: String(idx) };
|
|
864
|
+
}),
|
|
865
|
+
}]);
|
|
866
|
+
target = sessions[parseInt(sessionChoice, 10)] || sessions[0];
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
await execa('tmux', [...target.socketArgs, 'attach', '-t', target.name, '-r'], { stdio: 'inherit', reject: false });
|
|
870
|
+
logSessionEvent('host_mirror_attached', { session: target.name, source: target.source });
|
|
824
871
|
} catch (err) {
|
|
825
872
|
console.log(chalk.yellow(' ⚠️ Could not attach to tmux.'));
|
|
826
873
|
console.log(chalk.dim(` ${err.message}`));
|
|
@@ -884,7 +931,13 @@ async function hostDashboard(uid, password, serviceConfig, tunnelProcess, sessio
|
|
|
884
931
|
await execaCommand('powershell -NoProfile -Command "Get-CimInstance Win32_Process -Filter \\"name = \'sshd.exe\'\\" | Where-Object { $_.CommandLine -match \'sshd:.*@\' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force }"', { reject: false });
|
|
885
932
|
} else {
|
|
886
933
|
await execaCommand("pkill -f 'sshd:.*@'", { shell: true, reject: false });
|
|
887
|
-
await execa('tmux', [...tmuxSocketArgs(), 'kill-
|
|
934
|
+
await execa('tmux', [...tmuxSocketArgs(), 'kill-server'], { reject: false });
|
|
935
|
+
const legacySessions = await listTmuxSessions();
|
|
936
|
+
for (const session of legacySessions) {
|
|
937
|
+
if (isSecureLinkSession(session.name)) {
|
|
938
|
+
await execa('tmux', ['kill-session', '-t', session.name], { reject: false });
|
|
939
|
+
}
|
|
940
|
+
}
|
|
888
941
|
}
|
|
889
942
|
spinner.succeed('All client SSH sessions terminated');
|
|
890
943
|
logSessionEvent('host_sessions_terminated');
|