246 lines
6.8 KiB
TypeScript
246 lines
6.8 KiB
TypeScript
import * as plugins from './smartvpn.plugins.js';
|
|
import { VpnBridge } from './smartvpn.classes.vpnbridge.js';
|
|
import type {
|
|
IVpnServerOptions,
|
|
IVpnServerConfig,
|
|
IVpnStatus,
|
|
IVpnServerStatistics,
|
|
IVpnClientInfo,
|
|
IVpnKeypair,
|
|
IVpnClientTelemetry,
|
|
IWgPeerConfig,
|
|
IWgPeerInfo,
|
|
IClientEntry,
|
|
IClientConfigBundle,
|
|
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>);
|
|
}
|
|
|
|
/**
|
|
* Set rate limit for a specific client.
|
|
*/
|
|
public async setClientRateLimit(
|
|
clientId: string,
|
|
rateBytesPerSec: number,
|
|
burstBytes: number,
|
|
): Promise<void> {
|
|
await this.bridge.sendCommand('setClientRateLimit', {
|
|
clientId,
|
|
rateBytesPerSec,
|
|
burstBytes,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Remove rate limit for a specific client (unlimited).
|
|
*/
|
|
public async removeClientRateLimit(clientId: string): Promise<void> {
|
|
await this.bridge.sendCommand('removeClientRateLimit', { clientId });
|
|
}
|
|
|
|
/**
|
|
* Get telemetry for a specific client.
|
|
*/
|
|
public async getClientTelemetry(clientId: string): Promise<IVpnClientTelemetry> {
|
|
return this.bridge.sendCommand('getClientTelemetry', { clientId });
|
|
}
|
|
|
|
/**
|
|
* Generate a WireGuard-compatible X25519 keypair.
|
|
*/
|
|
public async generateWgKeypair(): Promise<IVpnKeypair> {
|
|
return this.bridge.sendCommand('generateWgKeypair', {} as Record<string, never>);
|
|
}
|
|
|
|
/**
|
|
* Add a WireGuard peer (server must be running in wireguard mode).
|
|
*/
|
|
public async addWgPeer(peer: IWgPeerConfig): Promise<void> {
|
|
await this.bridge.sendCommand('addWgPeer', { peer });
|
|
}
|
|
|
|
/**
|
|
* Remove a WireGuard peer by public key.
|
|
*/
|
|
public async removeWgPeer(publicKey: string): Promise<void> {
|
|
await this.bridge.sendCommand('removeWgPeer', { publicKey });
|
|
}
|
|
|
|
/**
|
|
* List WireGuard peers with stats.
|
|
*/
|
|
public async listWgPeers(): Promise<IWgPeerInfo[]> {
|
|
const result = await this.bridge.sendCommand('listWgPeers', {} as Record<string, never>);
|
|
return result.peers;
|
|
}
|
|
|
|
// ── Client Registry (Hub) Methods ─────────────────────────────────────
|
|
|
|
/**
|
|
* Create a new client. Generates keypairs, assigns IP, returns full config bundle.
|
|
* The secrets (private keys) are only returned at creation time.
|
|
*/
|
|
public async createClient(opts: Partial<IClientEntry>): Promise<IClientConfigBundle> {
|
|
return this.bridge.sendCommand('createClient', { client: opts });
|
|
}
|
|
|
|
/**
|
|
* Remove a registered client (also disconnects if connected).
|
|
*/
|
|
public async removeClient(clientId: string): Promise<void> {
|
|
await this.bridge.sendCommand('removeClient', { clientId });
|
|
}
|
|
|
|
/**
|
|
* Get a registered client by ID.
|
|
*/
|
|
public async getClient(clientId: string): Promise<IClientEntry> {
|
|
return this.bridge.sendCommand('getClient', { clientId });
|
|
}
|
|
|
|
/**
|
|
* List all registered clients.
|
|
*/
|
|
public async listRegisteredClients(): Promise<IClientEntry[]> {
|
|
const result = await this.bridge.sendCommand('listRegisteredClients', {} as Record<string, never>);
|
|
return result.clients;
|
|
}
|
|
|
|
/**
|
|
* Update a registered client's fields (ACLs, tags, description, etc.).
|
|
*/
|
|
public async updateClient(clientId: string, update: Partial<IClientEntry>): Promise<void> {
|
|
await this.bridge.sendCommand('updateClient', { clientId, update });
|
|
}
|
|
|
|
/**
|
|
* Enable a previously disabled client.
|
|
*/
|
|
public async enableClient(clientId: string): Promise<void> {
|
|
await this.bridge.sendCommand('enableClient', { clientId });
|
|
}
|
|
|
|
/**
|
|
* Disable a client (also disconnects if connected).
|
|
*/
|
|
public async disableClient(clientId: string): Promise<void> {
|
|
await this.bridge.sendCommand('disableClient', { clientId });
|
|
}
|
|
|
|
/**
|
|
* Rotate a client's keys. Returns a new config bundle with fresh keypairs.
|
|
*/
|
|
public async rotateClientKey(clientId: string): Promise<IClientConfigBundle> {
|
|
return this.bridge.sendCommand('rotateClientKey', { clientId });
|
|
}
|
|
|
|
/**
|
|
* Export a client config (without secrets) in the specified format.
|
|
*/
|
|
public async exportClientConfig(clientId: string, format: 'smartvpn' | 'wireguard'): Promise<string> {
|
|
const result = await this.bridge.sendCommand('exportClientConfig', { clientId, format });
|
|
return result.config;
|
|
}
|
|
|
|
/**
|
|
* Generate a standalone Noise IK keypair (not tied to a client).
|
|
*/
|
|
public async generateClientKeypair(): Promise<IVpnKeypair> {
|
|
return this.bridge.sendCommand('generateClientKeypair', {} 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;
|
|
}
|
|
}
|