initial
This commit is contained in:
8
ts/00_commitinfo_data.ts
Normal file
8
ts/00_commitinfo_data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitance info data
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartvpn',
|
||||
version: '1.0.0',
|
||||
description: 'A VPN solution with TypeScript control plane and Rust data plane daemon',
|
||||
};
|
||||
6
ts/index.ts
Normal file
6
ts/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './smartvpn.interfaces.js';
|
||||
export { VpnBridge } from './smartvpn.classes.vpnbridge.js';
|
||||
export { VpnClient } from './smartvpn.classes.vpnclient.js';
|
||||
export { VpnServer } from './smartvpn.classes.vpnserver.js';
|
||||
export { VpnConfig } from './smartvpn.classes.vpnconfig.js';
|
||||
export { VpnInstaller } from './smartvpn.classes.vpninstaller.js';
|
||||
152
ts/smartvpn.classes.vpnbridge.ts
Normal file
152
ts/smartvpn.classes.vpnbridge.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import * as plugins from './smartvpn.plugins.js';
|
||||
import * as paths from './smartvpn.paths.js';
|
||||
import type {
|
||||
TVpnTransportOptions,
|
||||
IVpnTransportSocket,
|
||||
} from './smartvpn.interfaces.js';
|
||||
import type { TCommandMap } from '@push.rocks/smartrust';
|
||||
|
||||
/**
|
||||
* Get the package root directory.
|
||||
*/
|
||||
function getPackageRoot(): string {
|
||||
const thisDir = plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url));
|
||||
return plugins.path.resolve(thisDir, '..');
|
||||
}
|
||||
|
||||
/**
|
||||
* Map Node.js platform/arch to tsrust's platform suffix.
|
||||
*/
|
||||
function getTsrustPlatformSuffix(): string | null {
|
||||
const archMap: Record<string, string> = { x64: 'amd64', arm64: 'arm64' };
|
||||
const osMap: Record<string, string> = { linux: 'linux', darwin: 'macos' };
|
||||
const os = osMap[process.platform];
|
||||
const arch = archMap[process.arch];
|
||||
if (os && arch) {
|
||||
return `${os}_${arch}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build local search paths for the smartvpn daemon binary.
|
||||
*/
|
||||
function buildLocalPaths(binaryName: string): string[] {
|
||||
const packageRoot = getPackageRoot();
|
||||
const suffix = getTsrustPlatformSuffix();
|
||||
const paths: string[] = [];
|
||||
|
||||
// dist_rust/ (tsrust cross-compiled output)
|
||||
if (suffix) {
|
||||
paths.push(plugins.path.join(packageRoot, 'dist_rust', `${binaryName}_${suffix}`));
|
||||
}
|
||||
paths.push(plugins.path.join(packageRoot, 'dist_rust', binaryName));
|
||||
|
||||
// Local dev build paths
|
||||
paths.push(plugins.path.resolve(process.cwd(), 'rust', 'target', 'release', binaryName));
|
||||
paths.push(plugins.path.resolve(process.cwd(), 'rust', 'target', 'debug', binaryName));
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared bridge wrapper around smartrust RustBridge.
|
||||
* Supports stdio mode (dev: spawn child process) and socket mode (production: connect to running daemon).
|
||||
*/
|
||||
export class VpnBridge<TCommands extends TCommandMap> extends plugins.events.EventEmitter {
|
||||
private bridge: plugins.smartrust.RustBridge<TCommands>;
|
||||
private transportOptions: TVpnTransportOptions;
|
||||
private mode: 'client' | 'server';
|
||||
|
||||
constructor(options: {
|
||||
transport: TVpnTransportOptions;
|
||||
mode: 'client' | 'server';
|
||||
binaryName?: string;
|
||||
}) {
|
||||
super();
|
||||
|
||||
const binaryName = options.binaryName || 'smartvpn_daemon';
|
||||
this.transportOptions = options.transport;
|
||||
this.mode = options.mode;
|
||||
|
||||
this.bridge = new plugins.smartrust.RustBridge<TCommands>({
|
||||
binaryName,
|
||||
envVarName: 'SMARTVPN_RUST_BINARY',
|
||||
platformPackagePrefix: '@push.rocks/smartvpn',
|
||||
localPaths: buildLocalPaths(binaryName),
|
||||
cliArgs: ['--management', '--mode', this.mode],
|
||||
maxPayloadSize: 10 * 1024 * 1024, // 10 MB
|
||||
});
|
||||
|
||||
// Forward events from inner bridge
|
||||
this.bridge.on('exit', (code: number | null, signal: string | null) => {
|
||||
this.emit('exit', code, signal);
|
||||
});
|
||||
this.bridge.on('reconnected', () => {
|
||||
this.emit('reconnected');
|
||||
});
|
||||
|
||||
// Forward management events from the daemon
|
||||
// smartrust emits 'management:<eventName>' for unsolicited events
|
||||
this.bridge.on('management:status', (data: any) => {
|
||||
this.emit('status', data);
|
||||
});
|
||||
this.bridge.on('management:error', (data: any) => {
|
||||
this.emit('error', data);
|
||||
});
|
||||
this.bridge.on('management:client-connected', (data: any) => {
|
||||
this.emit('client-connected', data);
|
||||
});
|
||||
this.bridge.on('management:client-disconnected', (data: any) => {
|
||||
this.emit('client-disconnected', data);
|
||||
});
|
||||
this.bridge.on('management:started', (data: any) => {
|
||||
this.emit('started', data);
|
||||
});
|
||||
this.bridge.on('management:stopped', (data: any) => {
|
||||
this.emit('stopped', data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the bridge: spawn in stdio mode or connect in socket mode.
|
||||
*/
|
||||
public async start(): Promise<boolean> {
|
||||
if (this.transportOptions.transport === 'socket') {
|
||||
const socketOpts = this.transportOptions as IVpnTransportSocket;
|
||||
return this.bridge.connect(socketOpts.socketPath, {
|
||||
autoReconnect: socketOpts.autoReconnect,
|
||||
reconnectBaseDelayMs: socketOpts.reconnectBaseDelayMs,
|
||||
reconnectMaxDelayMs: socketOpts.reconnectMaxDelayMs,
|
||||
maxReconnectAttempts: socketOpts.maxReconnectAttempts,
|
||||
});
|
||||
} else {
|
||||
return this.bridge.spawn();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the bridge. In socket mode, closes the socket (daemon stays alive).
|
||||
* In stdio mode, kills the child process.
|
||||
*/
|
||||
public stop(): void {
|
||||
this.bridge.kill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a typed command to the daemon.
|
||||
*/
|
||||
public async sendCommand<K extends string & keyof TCommands>(
|
||||
method: K,
|
||||
params: TCommands[K]['params'],
|
||||
): Promise<TCommands[K]['result']> {
|
||||
return this.bridge.sendCommand(method, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the bridge is currently running/connected.
|
||||
*/
|
||||
public get running(): boolean {
|
||||
return this.bridge.running;
|
||||
}
|
||||
}
|
||||
87
ts/smartvpn.classes.vpnclient.ts
Normal file
87
ts/smartvpn.classes.vpnclient.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import * as plugins from './smartvpn.plugins.js';
|
||||
import { VpnBridge } from './smartvpn.classes.vpnbridge.js';
|
||||
import type {
|
||||
IVpnClientOptions,
|
||||
IVpnClientConfig,
|
||||
IVpnStatus,
|
||||
IVpnStatistics,
|
||||
TVpnClientCommands,
|
||||
} from './smartvpn.interfaces.js';
|
||||
|
||||
/**
|
||||
* VPN Client — manages a smartvpn daemon in client mode.
|
||||
*/
|
||||
export class VpnClient extends plugins.events.EventEmitter {
|
||||
private bridge: VpnBridge<TVpnClientCommands>;
|
||||
private options: IVpnClientOptions;
|
||||
|
||||
constructor(options: IVpnClientOptions) {
|
||||
super();
|
||||
this.options = options;
|
||||
this.bridge = new VpnBridge<TVpnClientCommands>({
|
||||
transport: options.transport,
|
||||
mode: 'client',
|
||||
});
|
||||
|
||||
// Forward bridge events
|
||||
this.bridge.on('exit', (code: number | null, signal: string | null) => {
|
||||
this.emit('exit', { code, signal });
|
||||
});
|
||||
this.bridge.on('reconnected', () => {
|
||||
this.emit('reconnected');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the daemon bridge (spawn or connect).
|
||||
*/
|
||||
public async start(): Promise<boolean> {
|
||||
return this.bridge.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the VPN server using the provided config.
|
||||
*/
|
||||
public async connect(config?: IVpnClientConfig): Promise<{ assignedIp: string }> {
|
||||
const cfg = config || this.options.config;
|
||||
if (!cfg) {
|
||||
throw new Error('VpnClient.connect: no config provided');
|
||||
}
|
||||
return this.bridge.sendCommand('connect', { config: cfg });
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the VPN server.
|
||||
*/
|
||||
public async disconnect(): Promise<void> {
|
||||
await this.bridge.sendCommand('disconnect', {} as Record<string, never>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current connection status.
|
||||
*/
|
||||
public async getStatus(): Promise<IVpnStatus> {
|
||||
return this.bridge.sendCommand('getStatus', {} as Record<string, never>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get traffic statistics.
|
||||
*/
|
||||
public async getStatistics(): Promise<IVpnStatistics> {
|
||||
return this.bridge.sendCommand('getStatistics', {} as Record<string, never>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the daemon bridge.
|
||||
*/
|
||||
public stop(): void {
|
||||
this.bridge.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the bridge is running.
|
||||
*/
|
||||
public get running(): boolean {
|
||||
return this.bridge.running;
|
||||
}
|
||||
}
|
||||
104
ts/smartvpn.classes.vpnconfig.ts
Normal file
104
ts/smartvpn.classes.vpnconfig.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import * as plugins from './smartvpn.plugins.js';
|
||||
import type {
|
||||
IVpnClientConfig,
|
||||
IVpnServerConfig,
|
||||
} from './smartvpn.interfaces.js';
|
||||
|
||||
/**
|
||||
* VPN configuration loader, saver, and validator.
|
||||
*/
|
||||
export class VpnConfig {
|
||||
/**
|
||||
* Validate a client config object. Throws on invalid config.
|
||||
*/
|
||||
public static validateClientConfig(config: IVpnClientConfig): void {
|
||||
if (!config.serverUrl) {
|
||||
throw new Error('VpnConfig: serverUrl is required');
|
||||
}
|
||||
if (!config.serverUrl.startsWith('wss://') && !config.serverUrl.startsWith('ws://')) {
|
||||
throw new Error('VpnConfig: serverUrl must start with wss:// or ws://');
|
||||
}
|
||||
if (!config.serverPublicKey) {
|
||||
throw new Error('VpnConfig: serverPublicKey is required');
|
||||
}
|
||||
if (config.mtu !== undefined && (config.mtu < 576 || config.mtu > 65535)) {
|
||||
throw new Error('VpnConfig: mtu must be between 576 and 65535');
|
||||
}
|
||||
if (config.keepaliveIntervalSecs !== undefined && config.keepaliveIntervalSecs < 1) {
|
||||
throw new Error('VpnConfig: keepaliveIntervalSecs must be >= 1');
|
||||
}
|
||||
if (config.dns) {
|
||||
for (const dns of config.dns) {
|
||||
if (!VpnConfig.isValidIp(dns)) {
|
||||
throw new Error(`VpnConfig: invalid DNS address: ${dns}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a server config object. Throws on invalid config.
|
||||
*/
|
||||
public static validateServerConfig(config: IVpnServerConfig): void {
|
||||
if (!config.listenAddr) {
|
||||
throw new Error('VpnConfig: listenAddr is required');
|
||||
}
|
||||
if (!config.privateKey) {
|
||||
throw new Error('VpnConfig: privateKey is required');
|
||||
}
|
||||
if (!config.publicKey) {
|
||||
throw new Error('VpnConfig: publicKey is required');
|
||||
}
|
||||
if (!config.subnet) {
|
||||
throw new Error('VpnConfig: subnet is required');
|
||||
}
|
||||
if (!VpnConfig.isValidSubnet(config.subnet)) {
|
||||
throw new Error(`VpnConfig: invalid subnet: ${config.subnet}`);
|
||||
}
|
||||
if (config.mtu !== undefined && (config.mtu < 576 || config.mtu > 65535)) {
|
||||
throw new Error('VpnConfig: mtu must be between 576 and 65535');
|
||||
}
|
||||
if (config.keepaliveIntervalSecs !== undefined && config.keepaliveIntervalSecs < 1) {
|
||||
throw new Error('VpnConfig: keepaliveIntervalSecs must be >= 1');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a config from a JSON file.
|
||||
*/
|
||||
public static async loadFromFile<T>(filePath: string): Promise<T> {
|
||||
const content = await plugins.fs.promises.readFile(filePath, 'utf-8');
|
||||
return JSON.parse(content) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a config to a JSON file.
|
||||
*/
|
||||
public static async saveToFile<T>(filePath: string, config: T): Promise<void> {
|
||||
const content = JSON.stringify(config, null, 2);
|
||||
await plugins.fs.promises.writeFile(filePath, content, 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic IP address validation.
|
||||
*/
|
||||
private static isValidIp(ip: string): boolean {
|
||||
const parts = ip.split('.');
|
||||
if (parts.length !== 4) return false;
|
||||
return parts.every((part) => {
|
||||
const num = parseInt(part, 10);
|
||||
return !isNaN(num) && num >= 0 && num <= 255 && String(num) === part;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic subnet validation (CIDR notation).
|
||||
*/
|
||||
private static isValidSubnet(subnet: string): boolean {
|
||||
const [ip, prefix] = subnet.split('/');
|
||||
if (!ip || !prefix) return false;
|
||||
if (!VpnConfig.isValidIp(ip)) return false;
|
||||
const prefixNum = parseInt(prefix, 10);
|
||||
return !isNaN(prefixNum) && prefixNum >= 0 && prefixNum <= 32;
|
||||
}
|
||||
}
|
||||
126
ts/smartvpn.classes.vpninstaller.ts
Normal file
126
ts/smartvpn.classes.vpninstaller.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import * as plugins from './smartvpn.plugins.js';
|
||||
import type { TVpnPlatform, IVpnServiceUnit } from './smartvpn.interfaces.js';
|
||||
|
||||
/**
|
||||
* Install the smartvpn daemon as a system service.
|
||||
*/
|
||||
export class VpnInstaller {
|
||||
/**
|
||||
* Detect the current platform.
|
||||
*/
|
||||
public static detectPlatform(): TVpnPlatform {
|
||||
switch (process.platform) {
|
||||
case 'linux':
|
||||
return 'linux';
|
||||
case 'darwin':
|
||||
return 'macos';
|
||||
case 'win32':
|
||||
return 'windows';
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a systemd unit file for Linux.
|
||||
*/
|
||||
public static generateSystemdUnit(options: {
|
||||
binaryPath: string;
|
||||
socketPath: string;
|
||||
mode: 'client' | 'server';
|
||||
configPath?: string;
|
||||
description?: string;
|
||||
}): IVpnServiceUnit {
|
||||
const desc = options.description || `SmartVPN ${options.mode} daemon`;
|
||||
const content = `[Unit]
|
||||
Description=${desc}
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=${options.binaryPath} --management-socket ${options.socketPath} --mode ${options.mode}
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
LimitNOFILE=65535
|
||||
|
||||
# Security hardening
|
||||
NoNewPrivileges=no
|
||||
ProtectSystem=strict
|
||||
ProtectHome=yes
|
||||
ReadWritePaths=/var/run /dev/net/tun
|
||||
PrivateTmp=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`;
|
||||
|
||||
return {
|
||||
platform: 'linux',
|
||||
content,
|
||||
installPath: `/etc/systemd/system/smartvpn-${options.mode}.service`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a launchd plist for macOS.
|
||||
*/
|
||||
public static generateLaunchdPlist(options: {
|
||||
binaryPath: string;
|
||||
socketPath: string;
|
||||
mode: 'client' | 'server';
|
||||
description?: string;
|
||||
}): IVpnServiceUnit {
|
||||
const label = `rocks.push.smartvpn.${options.mode}`;
|
||||
const content = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>${label}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>${options.binaryPath}</string>
|
||||
<string>--management-socket</string>
|
||||
<string>${options.socketPath}</string>
|
||||
<string>--mode</string>
|
||||
<string>${options.mode}</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/smartvpn-${options.mode}.err.log</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/smartvpn-${options.mode}.out.log</string>
|
||||
</dict>
|
||||
</plist>
|
||||
`;
|
||||
|
||||
return {
|
||||
platform: 'macos',
|
||||
content,
|
||||
installPath: `/Library/LaunchDaemons/${label}.plist`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the appropriate service unit for the current platform.
|
||||
*/
|
||||
public static generateServiceUnit(options: {
|
||||
binaryPath: string;
|
||||
socketPath: string;
|
||||
mode: 'client' | 'server';
|
||||
}): IVpnServiceUnit {
|
||||
const platform = VpnInstaller.detectPlatform();
|
||||
switch (platform) {
|
||||
case 'linux':
|
||||
return VpnInstaller.generateSystemdUnit(options);
|
||||
case 'macos':
|
||||
return VpnInstaller.generateLaunchdPlist(options);
|
||||
default:
|
||||
throw new Error(`VpnInstaller: unsupported platform: ${platform}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
107
ts/smartvpn.classes.vpnserver.ts
Normal file
107
ts/smartvpn.classes.vpnserver.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import * as plugins from './smartvpn.plugins.js';
|
||||
import { VpnBridge } from './smartvpn.classes.vpnbridge.js';
|
||||
import type {
|
||||
IVpnServerOptions,
|
||||
IVpnServerConfig,
|
||||
IVpnStatus,
|
||||
IVpnServerStatistics,
|
||||
IVpnClientInfo,
|
||||
IVpnKeypair,
|
||||
TVpnServerCommands,
|
||||
} from './smartvpn.interfaces.js';
|
||||
|
||||
/**
|
||||
* VPN Server — manages a smartvpn daemon in server mode.
|
||||
*/
|
||||
export class VpnServer extends plugins.events.EventEmitter {
|
||||
private bridge: VpnBridge<TVpnServerCommands>;
|
||||
private options: IVpnServerOptions;
|
||||
|
||||
constructor(options: IVpnServerOptions) {
|
||||
super();
|
||||
this.options = options;
|
||||
this.bridge = new VpnBridge<TVpnServerCommands>({
|
||||
transport: options.transport,
|
||||
mode: 'server',
|
||||
});
|
||||
|
||||
// Forward bridge events
|
||||
this.bridge.on('exit', (code: number | null, signal: string | null) => {
|
||||
this.emit('exit', { code, signal });
|
||||
});
|
||||
this.bridge.on('reconnected', () => {
|
||||
this.emit('reconnected');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the daemon bridge (spawn or connect).
|
||||
*/
|
||||
public async start(config?: IVpnServerConfig): Promise<void> {
|
||||
const started = await this.bridge.start();
|
||||
if (!started) {
|
||||
throw new Error('VpnServer: failed to start daemon bridge');
|
||||
}
|
||||
const cfg = config || this.options.config;
|
||||
if (cfg) {
|
||||
await this.bridge.sendCommand('start', { config: cfg });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the VPN server.
|
||||
*/
|
||||
public async stopServer(): Promise<void> {
|
||||
await this.bridge.sendCommand('stop', {} as Record<string, never>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server status.
|
||||
*/
|
||||
public async getStatus(): Promise<IVpnStatus> {
|
||||
return this.bridge.sendCommand('getStatus', {} as Record<string, never>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get server statistics.
|
||||
*/
|
||||
public async getStatistics(): Promise<IVpnServerStatistics> {
|
||||
return this.bridge.sendCommand('getStatistics', {} as Record<string, never>);
|
||||
}
|
||||
|
||||
/**
|
||||
* List connected clients.
|
||||
*/
|
||||
public async listClients(): Promise<IVpnClientInfo[]> {
|
||||
const result = await this.bridge.sendCommand('listClients', {} as Record<string, never>);
|
||||
return result.clients;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect a specific client.
|
||||
*/
|
||||
public async disconnectClient(clientId: string): Promise<void> {
|
||||
await this.bridge.sendCommand('disconnectClient', { clientId });
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a new Noise keypair.
|
||||
*/
|
||||
public async generateKeypair(): Promise<IVpnKeypair> {
|
||||
return this.bridge.sendCommand('generateKeypair', {} as Record<string, never>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the daemon bridge.
|
||||
*/
|
||||
public stop(): void {
|
||||
this.bridge.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the bridge is running.
|
||||
*/
|
||||
public get running(): boolean {
|
||||
return this.bridge.running;
|
||||
}
|
||||
}
|
||||
166
ts/smartvpn.interfaces.ts
Normal file
166
ts/smartvpn.interfaces.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
// ============================================================================
|
||||
// Transport options
|
||||
// ============================================================================
|
||||
|
||||
export interface IVpnTransportStdio {
|
||||
transport: 'stdio';
|
||||
}
|
||||
|
||||
export interface IVpnTransportSocket {
|
||||
transport: 'socket';
|
||||
socketPath: string;
|
||||
autoReconnect?: boolean;
|
||||
reconnectBaseDelayMs?: number;
|
||||
reconnectMaxDelayMs?: number;
|
||||
maxReconnectAttempts?: number;
|
||||
}
|
||||
|
||||
export type TVpnTransportOptions = IVpnTransportStdio | IVpnTransportSocket;
|
||||
|
||||
// ============================================================================
|
||||
// Client configuration
|
||||
// ============================================================================
|
||||
|
||||
export interface IVpnClientConfig {
|
||||
/** Server WebSocket URL, e.g. wss://vpn.example.com/tunnel */
|
||||
serverUrl: string;
|
||||
/** Server's static public key (base64) for Noise NK handshake */
|
||||
serverPublicKey: string;
|
||||
/** Optional DNS servers to use while connected */
|
||||
dns?: string[];
|
||||
/** Optional MTU for the TUN device */
|
||||
mtu?: number;
|
||||
/** Keepalive interval in seconds (default: 30) */
|
||||
keepaliveIntervalSecs?: number;
|
||||
}
|
||||
|
||||
export interface IVpnClientOptions {
|
||||
transport: TVpnTransportOptions;
|
||||
config?: IVpnClientConfig;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Server configuration
|
||||
// ============================================================================
|
||||
|
||||
export interface IVpnServerConfig {
|
||||
/** Listen address for WebSocket, e.g. 0.0.0.0:443 */
|
||||
listenAddr: string;
|
||||
/** TLS certificate PEM (optional — can be behind reverse proxy) */
|
||||
tlsCert?: string;
|
||||
/** TLS private key PEM */
|
||||
tlsKey?: string;
|
||||
/** Server's Noise static private key (base64) */
|
||||
privateKey: string;
|
||||
/** Server's Noise static public key (base64) */
|
||||
publicKey: string;
|
||||
/** IP subnet for VPN clients, e.g. 10.8.0.0/24 */
|
||||
subnet: string;
|
||||
/** DNS servers pushed to clients */
|
||||
dns?: string[];
|
||||
/** MTU for TUN device */
|
||||
mtu?: number;
|
||||
/** Keepalive interval in seconds (default: 30) */
|
||||
keepaliveIntervalSecs?: number;
|
||||
/** Enable NAT/masquerade for client traffic */
|
||||
enableNat?: boolean;
|
||||
}
|
||||
|
||||
export interface IVpnServerOptions {
|
||||
transport: TVpnTransportOptions;
|
||||
config?: IVpnServerConfig;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Status and statistics
|
||||
// ============================================================================
|
||||
|
||||
export type TVpnConnectionState =
|
||||
| 'disconnected'
|
||||
| 'connecting'
|
||||
| 'handshaking'
|
||||
| 'connected'
|
||||
| 'reconnecting'
|
||||
| 'error';
|
||||
|
||||
export interface IVpnStatus {
|
||||
state: TVpnConnectionState;
|
||||
assignedIp?: string;
|
||||
serverAddr?: string;
|
||||
connectedSince?: string;
|
||||
lastError?: string;
|
||||
}
|
||||
|
||||
export interface IVpnStatistics {
|
||||
bytesSent: number;
|
||||
bytesReceived: number;
|
||||
packetsSent: number;
|
||||
packetsReceived: number;
|
||||
keepalivesSent: number;
|
||||
keepalivesReceived: number;
|
||||
uptimeSeconds: number;
|
||||
}
|
||||
|
||||
export interface IVpnClientInfo {
|
||||
clientId: string;
|
||||
assignedIp: string;
|
||||
connectedSince: string;
|
||||
bytesSent: number;
|
||||
bytesReceived: number;
|
||||
}
|
||||
|
||||
export interface IVpnServerStatistics extends IVpnStatistics {
|
||||
activeClients: number;
|
||||
totalConnections: number;
|
||||
}
|
||||
|
||||
export interface IVpnKeypair {
|
||||
publicKey: string;
|
||||
privateKey: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// IPC Command maps (used by smartrust RustBridge<TCommands>)
|
||||
// ============================================================================
|
||||
|
||||
export type TVpnClientCommands = {
|
||||
connect: { params: { config: IVpnClientConfig }; result: { assignedIp: string } };
|
||||
disconnect: { params: Record<string, never>; result: void };
|
||||
getStatus: { params: Record<string, never>; result: IVpnStatus };
|
||||
getStatistics: { params: Record<string, never>; result: IVpnStatistics };
|
||||
};
|
||||
|
||||
export type TVpnServerCommands = {
|
||||
start: { params: { config: IVpnServerConfig }; result: void };
|
||||
stop: { params: Record<string, never>; result: void };
|
||||
getStatus: { params: Record<string, never>; result: IVpnStatus };
|
||||
getStatistics: { params: Record<string, never>; result: IVpnServerStatistics };
|
||||
listClients: { params: Record<string, never>; result: { clients: IVpnClientInfo[] } };
|
||||
disconnectClient: { params: { clientId: string }; result: void };
|
||||
generateKeypair: { params: Record<string, never>; result: IVpnKeypair };
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Installer
|
||||
// ============================================================================
|
||||
|
||||
export type TVpnPlatform = 'linux' | 'macos' | 'windows' | 'unknown';
|
||||
|
||||
export interface IVpnServiceUnit {
|
||||
platform: TVpnPlatform;
|
||||
content: string;
|
||||
installPath: string;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Events emitted by VpnClient / VpnServer
|
||||
// ============================================================================
|
||||
|
||||
export interface IVpnEventMap {
|
||||
'status': IVpnStatus;
|
||||
'error': { message: string; code?: string };
|
||||
'client-connected': IVpnClientInfo;
|
||||
'client-disconnected': { clientId: string; reason?: string };
|
||||
'exit': { code: number | null; signal: string | null };
|
||||
'reconnected': void;
|
||||
}
|
||||
6
ts/smartvpn.paths.ts
Normal file
6
ts/smartvpn.paths.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import * as plugins from './smartvpn.plugins.js';
|
||||
|
||||
export const packageDir = plugins.path.join(
|
||||
plugins.smartpath.get.dirnameFromImportMetaUrl(import.meta.url),
|
||||
'../',
|
||||
);
|
||||
14
ts/smartvpn.plugins.ts
Normal file
14
ts/smartvpn.plugins.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// node native
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as url from 'url';
|
||||
import * as events from 'events';
|
||||
|
||||
export { path, fs, os, url, events };
|
||||
|
||||
// @push.rocks
|
||||
import * as smartpath from '@push.rocks/smartpath';
|
||||
import * as smartrust from '@push.rocks/smartrust';
|
||||
|
||||
export { smartpath, smartrust };
|
||||
Reference in New Issue
Block a user