Code cleanup
This commit is contained in:
@@ -22,33 +22,27 @@ const app = express();
|
||||
app.use(
|
||||
cors({
|
||||
origin: (origin, callback) => {
|
||||
// Allow requests with no origin (like mobile apps or curl requests)
|
||||
if (!origin) return callback(null, true);
|
||||
|
||||
// Allow localhost and 127.0.0.1 for development
|
||||
|
||||
const allowedOrigins = [
|
||||
"http://localhost:5173",
|
||||
"http://localhost:3000",
|
||||
"http://localhost:3000",
|
||||
"http://127.0.0.1:5173",
|
||||
"http://127.0.0.1:3000"
|
||||
"http://127.0.0.1:3000",
|
||||
];
|
||||
|
||||
// Allow any HTTPS origin (production deployments)
|
||||
|
||||
if (origin.startsWith("https://")) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// Allow any HTTP origin for self-hosted scenarios
|
||||
|
||||
if (origin.startsWith("http://")) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// Check against allowed development origins
|
||||
|
||||
if (allowedOrigins.includes(origin)) {
|
||||
return callback(null, true);
|
||||
}
|
||||
|
||||
// Reject other origins
|
||||
|
||||
callback(new Error("Not allowed by CORS"));
|
||||
},
|
||||
credentials: true,
|
||||
@@ -158,18 +152,15 @@ function getTunnelMarker(tunnelName: string) {
|
||||
return `TUNNEL_MARKER_${tunnelName.replace(/[^a-zA-Z0-9]/g, "_")}`;
|
||||
}
|
||||
|
||||
function cleanupTunnelResources(tunnelName: string, forceCleanup = false): void {
|
||||
tunnelLogger.info(`Cleaning up resources for tunnel '${tunnelName}' (force=${forceCleanup})`);
|
||||
|
||||
// Prevent concurrent cleanup operations
|
||||
function cleanupTunnelResources(
|
||||
tunnelName: string,
|
||||
forceCleanup = false,
|
||||
): void {
|
||||
if (cleanupInProgress.has(tunnelName)) {
|
||||
tunnelLogger.info(`Cleanup already in progress for '${tunnelName}', skipping`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Protect connecting tunnels unless forced
|
||||
if (!forceCleanup && tunnelConnecting.has(tunnelName)) {
|
||||
tunnelLogger.info(`Tunnel '${tunnelName}' is connecting, skipping cleanup (use force=true to override)`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -183,8 +174,6 @@ function cleanupTunnelResources(tunnelName: string, forceCleanup = false): void
|
||||
tunnelLogger.error(
|
||||
`Failed to kill remote tunnel for '${tunnelName}': ${err.message}`,
|
||||
);
|
||||
} else {
|
||||
tunnelLogger.info(`Successfully cleaned up remote tunnel processes for '${tunnelName}'`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -210,7 +199,6 @@ function cleanupTunnelResources(tunnelName: string, forceCleanup = false): void
|
||||
try {
|
||||
const conn = activeTunnels.get(tunnelName);
|
||||
if (conn) {
|
||||
tunnelLogger.info(`Closing SSH2 connection for tunnel '${tunnelName}'`);
|
||||
conn.end();
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -220,7 +208,6 @@ function cleanupTunnelResources(tunnelName: string, forceCleanup = false): void
|
||||
);
|
||||
}
|
||||
activeTunnels.delete(tunnelName);
|
||||
tunnelLogger.info(`Removed tunnel '${tunnelName}' from activeTunnels`);
|
||||
}
|
||||
|
||||
if (tunnelVerifications.has(tunnelName)) {
|
||||
@@ -454,10 +441,8 @@ async function connectSSHTunnel(
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark tunnel as connecting to protect from cleanup
|
||||
tunnelConnecting.add(tunnelName);
|
||||
|
||||
// Force cleanup any existing resources before new connection
|
||||
cleanupTunnelResources(tunnelName, true);
|
||||
|
||||
if (retryAttempt === 0) {
|
||||
@@ -519,7 +504,7 @@ async function connectSSHTunnel(
|
||||
const credential = credentials[0];
|
||||
resolvedSourceCredentials = {
|
||||
password: credential.password,
|
||||
sshKey: credential.privateKey || credential.key, // prefer new privateKey field
|
||||
sshKey: credential.privateKey || credential.key,
|
||||
keyPassword: credential.keyPassword,
|
||||
keyType: credential.keyType,
|
||||
authMethod: credential.authType,
|
||||
@@ -549,11 +534,10 @@ async function connectSSHTunnel(
|
||||
authMethod: tunnelConfig.endpointAuthMethod,
|
||||
};
|
||||
|
||||
tunnelLogger.info(`Source credentials for '${tunnelName}': authMethod=${resolvedSourceCredentials.authMethod}, hasPassword=${!!resolvedSourceCredentials.password}, hasSSHKey=${!!resolvedSourceCredentials.sshKey}`);
|
||||
tunnelLogger.info(`Final endpoint credentials for '${tunnelName}': authMethod=${resolvedEndpointCredentials.authMethod}, hasPassword=${!!resolvedEndpointCredentials.password}, hasSSHKey=${!!resolvedEndpointCredentials.sshKey}, credentialId=${tunnelConfig.endpointCredentialId}`);
|
||||
|
||||
// Validate that we have usable endpoint credentials
|
||||
if (resolvedEndpointCredentials.authMethod === "password" && !resolvedEndpointCredentials.password) {
|
||||
if (
|
||||
resolvedEndpointCredentials.authMethod === "password" &&
|
||||
!resolvedEndpointCredentials.password
|
||||
) {
|
||||
const errorMessage = `Cannot connect tunnel '${tunnelName}': endpoint host requires password authentication but no plaintext password available. Enable autostart for endpoint host or configure credentials in tunnel connection.`;
|
||||
tunnelLogger.error(errorMessage);
|
||||
broadcastTunnelStatus(tunnelName, {
|
||||
@@ -564,7 +548,10 @@ async function connectSSHTunnel(
|
||||
return;
|
||||
}
|
||||
|
||||
if (resolvedEndpointCredentials.authMethod === "key" && !resolvedEndpointCredentials.sshKey) {
|
||||
if (
|
||||
resolvedEndpointCredentials.authMethod === "key" &&
|
||||
!resolvedEndpointCredentials.sshKey
|
||||
) {
|
||||
const errorMessage = `Cannot connect tunnel '${tunnelName}': endpoint host requires key authentication but no plaintext key available. Enable autostart for endpoint host or configure credentials in tunnel connection.`;
|
||||
tunnelLogger.error(errorMessage);
|
||||
broadcastTunnelStatus(tunnelName, {
|
||||
@@ -591,12 +578,11 @@ async function connectSSHTunnel(
|
||||
const credential = credentials[0];
|
||||
resolvedEndpointCredentials = {
|
||||
password: credential.password,
|
||||
sshKey: credential.privateKey || credential.key, // prefer new privateKey field
|
||||
sshKey: credential.privateKey || credential.key,
|
||||
keyPassword: credential.keyPassword,
|
||||
keyType: credential.keyType,
|
||||
authMethod: credential.authType,
|
||||
};
|
||||
tunnelLogger.info(`Resolved endpoint credentials from DB for '${tunnelName}': authMethod=${resolvedEndpointCredentials.authMethod}, hasPassword=${!!resolvedEndpointCredentials.password}, hasSSHKey=${!!resolvedEndpointCredentials.sshKey}`);
|
||||
} else {
|
||||
tunnelLogger.warn("No endpoint credentials found in database", {
|
||||
operation: "tunnel_connect",
|
||||
@@ -646,7 +632,6 @@ async function connectSSHTunnel(
|
||||
clearTimeout(connectionTimeout);
|
||||
tunnelLogger.error(`SSH error for '${tunnelName}': ${err.message}`);
|
||||
|
||||
// Clear connecting state on error
|
||||
tunnelConnecting.delete(tunnelName);
|
||||
|
||||
if (activeRetryTimers.has(tunnelName)) {
|
||||
@@ -677,7 +662,6 @@ async function connectSSHTunnel(
|
||||
conn.on("close", () => {
|
||||
clearTimeout(connectionTimeout);
|
||||
|
||||
// Clear connecting state on close
|
||||
tunnelConnecting.delete(tunnelName);
|
||||
|
||||
if (activeRetryTimers.has(tunnelName)) {
|
||||
@@ -722,8 +706,6 @@ async function connectSSHTunnel(
|
||||
tunnelCmd = `exec -a "${tunnelMarker}" sshpass -p '${resolvedEndpointCredentials.password || ""}' ssh -v -N -o StrictHostKeyChecking=no -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o GatewayPorts=yes -R ${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort} ${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}`;
|
||||
}
|
||||
|
||||
tunnelLogger.info(`Executing tunnel command for '${tunnelName}': ${tunnelCmd.replace(/sshpass -p '[^']*'/g, 'sshpass -p [HIDDEN]').replace(/echo '[^']*'/g, 'echo [HIDDEN]')}`);
|
||||
|
||||
conn.exec(tunnelCmd, (err, stream) => {
|
||||
if (err) {
|
||||
tunnelLogger.error(
|
||||
@@ -750,7 +732,6 @@ async function connectSSHTunnel(
|
||||
!manualDisconnects.has(tunnelName) &&
|
||||
activeTunnels.has(tunnelName)
|
||||
) {
|
||||
// Clear connecting state on successful connection
|
||||
tunnelConnecting.delete(tunnelName);
|
||||
|
||||
broadcastTunnelStatus(tunnelName, {
|
||||
@@ -827,7 +808,6 @@ async function connectSSHTunnel(
|
||||
stream.stdout?.on("data", (data: Buffer) => {
|
||||
const output = data.toString().trim();
|
||||
if (output) {
|
||||
tunnelLogger.info(`SSH stdout for '${tunnelName}': ${output}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -836,25 +816,42 @@ async function connectSSHTunnel(
|
||||
stream.stderr.on("data", (data) => {
|
||||
const errorMsg = data.toString().trim();
|
||||
if (errorMsg) {
|
||||
tunnelLogger.error(`SSH stderr for '${tunnelName}': ${errorMsg}`);
|
||||
const isDebugMessage =
|
||||
errorMsg.startsWith("debug1:") ||
|
||||
errorMsg.startsWith("debug2:") ||
|
||||
errorMsg.startsWith("debug3:") ||
|
||||
errorMsg.includes("Reading configuration data") ||
|
||||
errorMsg.includes("include /etc/ssh/ssh_config.d") ||
|
||||
errorMsg.includes("matched no files") ||
|
||||
errorMsg.includes("Applying options for");
|
||||
|
||||
// Check for specific SSH errors
|
||||
if (errorMsg.includes("sshpass: command not found") || errorMsg.includes("sshpass not found")) {
|
||||
if (!isDebugMessage) {
|
||||
tunnelLogger.error(`SSH stderr for '${tunnelName}': ${errorMsg}`);
|
||||
}
|
||||
|
||||
if (
|
||||
errorMsg.includes("sshpass: command not found") ||
|
||||
errorMsg.includes("sshpass not found")
|
||||
) {
|
||||
broadcastTunnelStatus(tunnelName, {
|
||||
connected: false,
|
||||
status: CONNECTION_STATES.FAILED,
|
||||
reason: "sshpass tool not found on source host. Please install sshpass or use SSH key authentication.",
|
||||
reason:
|
||||
"sshpass tool not found on source host. Please install sshpass or use SSH key authentication.",
|
||||
});
|
||||
}
|
||||
|
||||
// Check for port forwarding errors
|
||||
if (errorMsg.includes("remote port forwarding failed") || errorMsg.includes("Error: remote port forwarding failed")) {
|
||||
if (
|
||||
errorMsg.includes("remote port forwarding failed") ||
|
||||
errorMsg.includes("Error: remote port forwarding failed")
|
||||
) {
|
||||
const portMatch = errorMsg.match(/listen port (\d+)/);
|
||||
const port = portMatch ? portMatch[1] : tunnelConfig.endpointPort;
|
||||
|
||||
tunnelLogger.error(`Port forwarding failed for tunnel '${tunnelName}' on port ${port}. This prevents tunnel establishment.`);
|
||||
tunnelLogger.error(
|
||||
`Port forwarding failed for tunnel '${tunnelName}' on port ${port}. This prevents tunnel establishment.`,
|
||||
);
|
||||
|
||||
// Close the connection immediately to prevent retries
|
||||
if (activeTunnels.has(tunnelName)) {
|
||||
const conn = activeTunnels.get(tunnelName);
|
||||
if (conn) {
|
||||
@@ -905,7 +902,14 @@ async function connectSSHTunnel(
|
||||
"aes256-cbc",
|
||||
"3des-cbc",
|
||||
],
|
||||
hmac: ["hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", "hmac-md5"],
|
||||
hmac: [
|
||||
"hmac-sha2-256-etm@openssh.com",
|
||||
"hmac-sha2-512-etm@openssh.com",
|
||||
"hmac-sha2-256",
|
||||
"hmac-sha2-512",
|
||||
"hmac-sha1",
|
||||
"hmac-md5",
|
||||
],
|
||||
compress: ["none", "zlib@openssh.com", "zlib"],
|
||||
},
|
||||
};
|
||||
@@ -975,9 +979,7 @@ async function killRemoteTunnelByMarker(
|
||||
callback: (err?: Error) => void,
|
||||
) {
|
||||
const tunnelMarker = getTunnelMarker(tunnelName);
|
||||
tunnelLogger.info(`Attempting to kill remote tunnel processes with marker '${tunnelMarker}' on source host ${tunnelConfig.sourceIP}`);
|
||||
|
||||
// Resolve source credentials using same logic as main tunnel connection
|
||||
let resolvedSourceCredentials = {
|
||||
password: tunnelConfig.sourcePassword,
|
||||
sshKey: tunnelConfig.sourceSSHKey,
|
||||
@@ -1049,7 +1051,14 @@ async function killRemoteTunnelByMarker(
|
||||
"aes256-cbc",
|
||||
"3des-cbc",
|
||||
],
|
||||
hmac: ["hmac-sha2-256-etm@openssh.com", "hmac-sha2-512-etm@openssh.com", "hmac-sha2-256", "hmac-sha2-512", "hmac-sha1", "hmac-md5"],
|
||||
hmac: [
|
||||
"hmac-sha2-256-etm@openssh.com",
|
||||
"hmac-sha2-512-etm@openssh.com",
|
||||
"hmac-sha2-256",
|
||||
"hmac-sha2-512",
|
||||
"hmac-sha1",
|
||||
"hmac-md5",
|
||||
],
|
||||
compress: ["none", "zlib@openssh.com", "zlib"],
|
||||
},
|
||||
};
|
||||
@@ -1085,7 +1094,6 @@ async function killRemoteTunnelByMarker(
|
||||
}
|
||||
|
||||
conn.on("ready", () => {
|
||||
// First, check for existing processes and get their PIDs
|
||||
const checkCmd = `ps aux | grep -E '(${tunnelMarker}|ssh.*-R.*${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort}.*${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}|sshpass.*ssh.*-R.*${tunnelConfig.endpointPort})' | grep -v grep`;
|
||||
|
||||
conn.exec(checkCmd, (err, stream) => {
|
||||
@@ -1095,31 +1103,27 @@ async function killRemoteTunnelByMarker(
|
||||
const output = data.toString().trim();
|
||||
if (output) {
|
||||
foundProcesses = true;
|
||||
tunnelLogger.info(`Found running tunnel processes for '${tunnelName}': ${output}`);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on("close", () => {
|
||||
if (!foundProcesses) {
|
||||
tunnelLogger.info(`No running tunnel processes found for '${tunnelName}', cleanup not needed`);
|
||||
conn.end();
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute kill commands sequentially for better control
|
||||
const killCmds = [
|
||||
`pkill -TERM -f '${tunnelMarker}'`,
|
||||
`sleep 1 && pkill -f 'ssh.*-R.*${tunnelConfig.endpointPort}:localhost:${tunnelConfig.sourcePort}.*${tunnelConfig.endpointUsername}@${tunnelConfig.endpointIP}'`,
|
||||
`sleep 1 && pkill -f 'sshpass.*ssh.*-R.*${tunnelConfig.endpointPort}'`,
|
||||
`sleep 2 && pkill -9 -f '${tunnelMarker}'`, // Force kill after delay
|
||||
`sleep 2 && pkill -9 -f '${tunnelMarker}'`,
|
||||
];
|
||||
|
||||
let commandIndex = 0;
|
||||
|
||||
function executeNextKillCommand() {
|
||||
if (commandIndex >= killCmds.length) {
|
||||
// Final verification
|
||||
conn.exec(checkCmd, (err, verifyStream) => {
|
||||
let stillRunning = false;
|
||||
|
||||
@@ -1127,15 +1131,17 @@ async function killRemoteTunnelByMarker(
|
||||
const output = data.toString().trim();
|
||||
if (output) {
|
||||
stillRunning = true;
|
||||
tunnelLogger.warn(`Processes still running after cleanup for '${tunnelName}': ${output}`);
|
||||
tunnelLogger.warn(
|
||||
`Processes still running after cleanup for '${tunnelName}': ${output}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
verifyStream.on("close", () => {
|
||||
if (!stillRunning) {
|
||||
tunnelLogger.info(`All tunnel processes successfully terminated for '${tunnelName}'`);
|
||||
} else {
|
||||
tunnelLogger.warn(`Some tunnel processes may still be running for '${tunnelName}'`);
|
||||
if (stillRunning) {
|
||||
tunnelLogger.warn(
|
||||
`Some tunnel processes may still be running for '${tunnelName}'`,
|
||||
);
|
||||
}
|
||||
conn.end();
|
||||
callback();
|
||||
@@ -1148,13 +1154,13 @@ async function killRemoteTunnelByMarker(
|
||||
|
||||
conn.exec(killCmd, (err, stream) => {
|
||||
if (err) {
|
||||
tunnelLogger.warn(`Kill command ${commandIndex + 1} failed for '${tunnelName}': ${err.message}`);
|
||||
tunnelLogger.warn(
|
||||
`Kill command ${commandIndex + 1} failed for '${tunnelName}': ${err.message}`,
|
||||
);
|
||||
} else {
|
||||
tunnelLogger.info(`Executed kill command ${commandIndex + 1} for '${tunnelName}': ${killCmd.replace(/sleep \d+ && /, '')}`);
|
||||
}
|
||||
|
||||
stream.on("close", (code) => {
|
||||
tunnelLogger.info(`Kill command ${commandIndex + 1} completed with code ${code} for '${tunnelName}'`);
|
||||
commandIndex++;
|
||||
executeNextKillCommand();
|
||||
});
|
||||
@@ -1162,14 +1168,15 @@ async function killRemoteTunnelByMarker(
|
||||
stream.on("data", (data) => {
|
||||
const output = data.toString().trim();
|
||||
if (output) {
|
||||
tunnelLogger.info(`Kill command ${commandIndex + 1} output for '${tunnelName}': ${output}`);
|
||||
}
|
||||
});
|
||||
|
||||
stream.stderr.on("data", (data) => {
|
||||
const output = data.toString().trim();
|
||||
if (output && !output.includes("debug1")) {
|
||||
tunnelLogger.warn(`Kill command ${commandIndex + 1} stderr for '${tunnelName}': ${output}`);
|
||||
tunnelLogger.warn(
|
||||
`Kill command ${commandIndex + 1} stderr for '${tunnelName}': ${output}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1181,7 +1188,9 @@ async function killRemoteTunnelByMarker(
|
||||
});
|
||||
|
||||
conn.on("error", (err) => {
|
||||
tunnelLogger.error(`Failed to connect to source host for killing tunnel '${tunnelName}': ${err.message}`);
|
||||
tunnelLogger.error(
|
||||
`Failed to connect to source host for killing tunnel '${tunnelName}': ${err.message}`,
|
||||
);
|
||||
callback(err);
|
||||
});
|
||||
|
||||
@@ -1212,8 +1221,6 @@ app.post("/ssh/tunnel/connect", (req, res) => {
|
||||
|
||||
const tunnelName = tunnelConfig.name;
|
||||
|
||||
// Clean up any existing resources before starting new connection
|
||||
tunnelLogger.info(`Starting new connection for '${tunnelName}', cleaning up any existing resources`);
|
||||
cleanupTunnelResources(tunnelName);
|
||||
|
||||
manualDisconnects.delete(tunnelName);
|
||||
@@ -1247,8 +1254,6 @@ app.post("/ssh/tunnel/disconnect", (req, res) => {
|
||||
activeRetryTimers.delete(tunnelName);
|
||||
}
|
||||
|
||||
// Immediately clean up active connections (force cleanup)
|
||||
tunnelLogger.info(`Manual disconnect requested for '${tunnelName}', cleaning up resources`);
|
||||
cleanupTunnelResources(tunnelName, true);
|
||||
|
||||
broadcastTunnelStatus(tunnelName, {
|
||||
@@ -1287,8 +1292,6 @@ app.post("/ssh/tunnel/cancel", (req, res) => {
|
||||
countdownIntervals.delete(tunnelName);
|
||||
}
|
||||
|
||||
// Immediately clean up active connections for cancel operation too (force cleanup)
|
||||
tunnelLogger.info(`Cancel requested for '${tunnelName}', cleaning up resources`);
|
||||
cleanupTunnelResources(tunnelName, true);
|
||||
|
||||
broadcastTunnelStatus(tunnelName, {
|
||||
@@ -1309,11 +1312,9 @@ app.post("/ssh/tunnel/cancel", (req, res) => {
|
||||
|
||||
async function initializeAutoStartTunnels(): Promise<void> {
|
||||
try {
|
||||
// Get internal auth token from SystemCrypto
|
||||
const systemCrypto = SystemCrypto.getInstance();
|
||||
const internalAuthToken = await systemCrypto.getInternalAuthToken();
|
||||
|
||||
// Get autostart hosts for tunnel configs
|
||||
const autostartResponse = await axios.get(
|
||||
"http://localhost:30001/ssh/db/host/internal",
|
||||
{
|
||||
@@ -1324,7 +1325,6 @@ async function initializeAutoStartTunnels(): Promise<void> {
|
||||
},
|
||||
);
|
||||
|
||||
// Get all hosts for endpointHost resolution
|
||||
const allHostsResponse = await axios.get(
|
||||
"http://localhost:30001/ssh/db/host/internal/all",
|
||||
{
|
||||
@@ -1339,7 +1339,9 @@ async function initializeAutoStartTunnels(): Promise<void> {
|
||||
const allHosts: SSHHost[] = allHostsResponse.data || [];
|
||||
const autoStartTunnels: TunnelConfig[] = [];
|
||||
|
||||
tunnelLogger.info(`Found ${autostartHosts.length} autostart hosts and ${allHosts.length} total hosts for endpointHost resolution`);
|
||||
tunnelLogger.info(
|
||||
`Found ${autostartHosts.length} autostart hosts and ${allHosts.length} total hosts for endpointHost resolution`,
|
||||
);
|
||||
|
||||
for (const host of autostartHosts) {
|
||||
if (host.enableTunnel && host.tunnelConnections) {
|
||||
@@ -1352,50 +1354,39 @@ async function initializeAutoStartTunnels(): Promise<void> {
|
||||
);
|
||||
|
||||
if (endpointHost) {
|
||||
tunnelLogger.info(`Setting up tunnel credentials for '${host.name || `${host.username}@${host.ip}`}' -> '${endpointHost.name || `${endpointHost.username}@${endpointHost.ip}`}': sourceAutostart=${!!host.autostartPassword}, endpointAutostart=${!!endpointHost.autostartPassword}, endpointEncrypted=${!!endpointHost.password}`);
|
||||
|
||||
// Debug: Log actual credential availability
|
||||
tunnelLogger.info(`Source host credentials debug:`, {
|
||||
hostId: host.id,
|
||||
hasAutostartPassword: !!host.autostartPassword,
|
||||
hasAutostartKey: !!host.autostartKey,
|
||||
hasEncryptedPassword: !!host.password,
|
||||
hasEncryptedKey: !!host.key,
|
||||
authType: host.authType
|
||||
});
|
||||
|
||||
tunnelLogger.info(`Endpoint host credentials debug:`, {
|
||||
hostId: endpointHost.id,
|
||||
hasAutostartPassword: !!endpointHost.autostartPassword,
|
||||
hasAutostartKey: !!endpointHost.autostartKey,
|
||||
hasEncryptedPassword: !!endpointHost.password,
|
||||
hasEncryptedKey: !!endpointHost.key,
|
||||
authType: endpointHost.authType
|
||||
});
|
||||
|
||||
const tunnelConfig: TunnelConfig = {
|
||||
name: `${host.name || `${host.username}@${host.ip}`}_${tunnelConnection.sourcePort}_${tunnelConnection.endpointPort}`,
|
||||
hostName: host.name || `${host.username}@${host.ip}`,
|
||||
sourceIP: host.ip,
|
||||
sourceSSHPort: host.port,
|
||||
sourceUsername: host.username,
|
||||
// Prefer autostart credentials for source host, fallback to encrypted credentials
|
||||
sourcePassword: host.autostartPassword || host.password,
|
||||
sourceAuthMethod: host.authType,
|
||||
sourceSSHKey: host.autostartKey || host.key,
|
||||
sourceKeyPassword: host.autostartKeyPassword || host.keyPassword,
|
||||
sourceKeyPassword:
|
||||
host.autostartKeyPassword || host.keyPassword,
|
||||
sourceKeyType: host.keyType,
|
||||
sourceCredentialId: host.credentialId,
|
||||
sourceUserId: host.userId,
|
||||
endpointIP: endpointHost.ip,
|
||||
endpointSSHPort: endpointHost.port,
|
||||
endpointUsername: endpointHost.username,
|
||||
// Prefer TunnelConnection credentials, then autostart credentials, fallback to encrypted credentials
|
||||
endpointPassword: tunnelConnection.endpointPassword || endpointHost.autostartPassword || endpointHost.password,
|
||||
endpointAuthMethod: tunnelConnection.endpointAuthType || endpointHost.authType,
|
||||
endpointSSHKey: tunnelConnection.endpointKey || endpointHost.autostartKey || endpointHost.key,
|
||||
endpointKeyPassword: tunnelConnection.endpointKeyPassword || endpointHost.autostartKeyPassword || endpointHost.keyPassword,
|
||||
endpointKeyType: tunnelConnection.endpointKeyType || endpointHost.keyType,
|
||||
endpointPassword:
|
||||
tunnelConnection.endpointPassword ||
|
||||
endpointHost.autostartPassword ||
|
||||
endpointHost.password,
|
||||
endpointAuthMethod:
|
||||
tunnelConnection.endpointAuthType || endpointHost.authType,
|
||||
endpointSSHKey:
|
||||
tunnelConnection.endpointKey ||
|
||||
endpointHost.autostartKey ||
|
||||
endpointHost.key,
|
||||
endpointKeyPassword:
|
||||
tunnelConnection.endpointKeyPassword ||
|
||||
endpointHost.autostartKeyPassword ||
|
||||
endpointHost.keyPassword,
|
||||
endpointKeyType:
|
||||
tunnelConnection.endpointKeyType || endpointHost.keyType,
|
||||
endpointCredentialId: endpointHost.credentialId,
|
||||
endpointUserId: endpointHost.userId,
|
||||
sourcePort: tunnelConnection.sourcePort,
|
||||
@@ -1406,24 +1397,30 @@ async function initializeAutoStartTunnels(): Promise<void> {
|
||||
isPinned: host.pin,
|
||||
};
|
||||
|
||||
// Validate source and endpoint credentials availability
|
||||
const hasSourcePassword = host.autostartPassword;
|
||||
const hasSourceKey = host.autostartKey;
|
||||
const hasEndpointPassword = tunnelConnection.endpointPassword || endpointHost.autostartPassword;
|
||||
const hasEndpointKey = tunnelConnection.endpointKey || endpointHost.autostartKey;
|
||||
const hasEndpointPassword =
|
||||
tunnelConnection.endpointPassword ||
|
||||
endpointHost.autostartPassword;
|
||||
const hasEndpointKey =
|
||||
tunnelConnection.endpointKey || endpointHost.autostartKey;
|
||||
|
||||
if (!hasSourcePassword && !hasSourceKey) {
|
||||
tunnelLogger.warn(`Tunnel '${tunnelConfig.name}' may fail: source host '${host.name || `${host.username}@${host.ip}`}' has no plaintext credentials. Enable autostart for this host to use unattended tunneling.`);
|
||||
tunnelLogger.warn(
|
||||
`Tunnel '${tunnelConfig.name}' may fail: source host '${host.name || `${host.username}@${host.ip}`}' has no plaintext credentials. Enable autostart for this host to use unattended tunneling.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasEndpointPassword && !hasEndpointKey) {
|
||||
tunnelLogger.warn(`Tunnel '${tunnelConfig.name}' may fail: endpoint host '${endpointHost.name || `${endpointHost.username}@${endpointHost.ip}`}' has no plaintext credentials. Consider enabling autostart for this host or configuring credentials in tunnel connection.`);
|
||||
tunnelLogger.warn(
|
||||
`Tunnel '${tunnelConfig.name}' may fail: endpoint host '${endpointHost.name || `${endpointHost.username}@${endpointHost.ip}`}' has no plaintext credentials. Consider enabling autostart for this host or configuring credentials in tunnel connection.`,
|
||||
);
|
||||
}
|
||||
|
||||
autoStartTunnels.push(tunnelConfig);
|
||||
} else {
|
||||
tunnelLogger.error(
|
||||
`Failed to find endpointHost '${tunnelConnection.endpointHost}' for tunnel from ${host.name || `${host.username}@${host.ip}`}. Available hosts: ${allHosts.map(h => h.name || `${h.username}@${h.ip}`).join(', ')}`,
|
||||
`Failed to find endpointHost '${tunnelConnection.endpointHost}' for tunnel from ${host.name || `${host.username}@${host.ip}`}. Available hosts: ${allHosts.map((h) => h.name || `${h.username}@${h.ip}`).join(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1431,8 +1428,6 @@ async function initializeAutoStartTunnels(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
tunnelLogger.info(`Found ${autoStartTunnels.length} auto-start tunnels`);
|
||||
|
||||
for (const tunnelConfig of autoStartTunnels) {
|
||||
tunnelConfigs.set(tunnelConfig.name, tunnelConfig);
|
||||
|
||||
@@ -1454,10 +1449,6 @@ async function initializeAutoStartTunnels(): Promise<void> {
|
||||
|
||||
const PORT = 30003;
|
||||
app.listen(PORT, () => {
|
||||
tunnelLogger.success("SSH Tunnel API server started", {
|
||||
operation: "server_start",
|
||||
port: PORT,
|
||||
});
|
||||
setTimeout(() => {
|
||||
initializeAutoStartTunnels();
|
||||
}, 2000);
|
||||
|
||||
Reference in New Issue
Block a user