vite-plugin-ssh-tunnel
Version:
Vite plugin to set up a reverse SSH tunnel for reverse proxies
87 lines (71 loc) • 2.59 kB
JavaScript
import { execFileSync } from 'node:child_process';
import { existsSync, mkdirSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import pc from 'picocolors';
import { quote } from "shell-quote";
const tmpFolderPath = join(tmpdir(), 'vite-plugin-ssh-tunnel');
mkdirSync(tmpFolderPath, { recursive: true });
const socketPath = join(tmpFolderPath, 'socket');
const closeTunnel = () => {
if (existsSync(socketPath)) {
execFileSync('ssh', ['-S', socketPath, '-O', 'exit', 'dummy.com']);
}
};
// noinspection JSUnusedGlobalSymbols
/**
* Create a plugin that creates an SSH tunnel to a remote server.
* @param {import('./index.d.ts').Config} config - The configuration object for the plugin.
* @returns {import('vite').Plugin}
*/
export const sshTunnel = (config) => ({
name: 'ssh-tunnel', configureServer(server) {
const { httpServer } = server;
const log = (color, message) => {
server.config.logger.info(pc.magenta(' ➜') + pc.magenta(' tunnel: ') + pc[color](message));
}
httpServer?.on('listening', async () => {
const address = httpServer.address();
if (address === null || typeof address === 'string') {
return;
}
const privateKey = resolve(config.privateKey);
if (!existsSync(privateKey)) {
log('red', `Private key file not found: ${config.privateKey}`);
return;
}
const port = address.port;
const remotePort = Math.min(
Math.max(
Number.isInteger(config.remotePort) ? config.remotePort : 3000,
1
),
65535
);
const username = quote([config.username]);
const host = quote([config.host]);
closeTunnel();
execFileSync('ssh', [
'-i',
privateKey,
'-o',
'ExitOnForwardFailure=yes',
'-o',
'ServerAliveInterval=30',
'-f',
'-N',
'-M',
'-S',
socketPath,
'-R',
`0.0.0.0:${remotePort}:localhost:${port}`,
`${username}@${host}`
]);
log('cyan', config.proxyUrl ?? `https://${config.host}`);
});
httpServer?.on('close', () => {
closeTunnel();
log('red', 'closed');
});
}
});