import { parseCidr } from 'cidr-tools'; import { stringifyIp } from 'ip-bigint'; import type { ClientType } from '#db/repositories/client/types'; import type { InterfaceType } from '#db/repositories/interface/types'; import type { UserConfigType } from '#db/repositories/userConfig/types'; import type { HooksType } from '#db/repositories/hooks/types'; export const wg = { generateServerPeer: (client: Omit) => { const allowedIps = [ `${client.ipv4Address}/32`, `${client.ipv6Address}/128`, ...(client.serverAllowedIps ?? []), ]; const extraLines = []; if (client.serverEndpoint) { extraLines.push(`Endpoint = ${client.serverEndpoint}`); } return `# Client: ${client.name} (${client.id}) [Peer] PublicKey = ${client.publicKey} PresharedKey = ${client.preSharedKey} AllowedIPs = ${allowedIps.join(', ')}${extraLines.length ? `\n${extraLines.join('\n')}` : ''}`; }, generateServerInterface: (wgInterface: InterfaceType, hooks: HooksType) => { const cidr4 = parseCidr(wgInterface.ipv4Cidr); const cidr6 = parseCidr(wgInterface.ipv6Cidr); const ipv4Addr = stringifyIp({ number: cidr4.start + 1n, version: 4 }); const ipv6Addr = stringifyIp({ number: cidr6.start + 1n, version: 6 }); return `# Note: Do not edit this file directly. # Your changes will be overwritten! # Server [Interface] PrivateKey = ${wgInterface.privateKey} Address = ${ipv4Addr}/${cidr4.prefix}, ${ipv6Addr}/${cidr6.prefix} ListenPort = ${wgInterface.port} MTU = ${wgInterface.mtu} PreUp = ${iptablesTemplate(hooks.preUp, wgInterface)} PostUp = ${iptablesTemplate(hooks.postUp, wgInterface)} PreDown = ${iptablesTemplate(hooks.preDown, wgInterface)} PostDown = ${iptablesTemplate(hooks.postDown, wgInterface)}`; }, generateClientConfig: ( wgInterface: InterfaceType, userConfig: UserConfigType, client: ClientType ) => { const cidr4Block = parseCidr(wgInterface.ipv4Cidr).prefix; const cidr6Block = parseCidr(wgInterface.ipv6Cidr).prefix; const hookLines = [ client.preUp ? `PreUp = ${client.preUp}` : null, client.postUp ? `PostUp = ${client.postUp}` : null, client.preDown ? `PreDown = ${client.preDown}` : null, client.postDown ? `PostDown = ${client.postDown}` : null, ].filter((v) => v !== null); return `[Interface] PrivateKey = ${client.privateKey} Address = ${client.ipv4Address}/${cidr4Block}, ${client.ipv6Address}/${cidr6Block} DNS = ${(client.dns ?? userConfig.defaultDns).join(', ')} MTU = ${client.mtu} ${hookLines.length ? `${hookLines.join('\n')}\n` : ''} [Peer] PublicKey = ${wgInterface.publicKey} PresharedKey = ${client.preSharedKey} AllowedIPs = ${(client.allowedIps ?? userConfig.defaultAllowedIps).join(', ')} PersistentKeepalive = ${client.persistentKeepalive} Endpoint = ${userConfig.host}:${userConfig.port}`; }, generatePrivateKey: () => { return exec('wg genkey'); }, getPublicKey: (privateKey: string) => { return exec(`echo ${privateKey} | wg pubkey`, { log: 'echo ***hidden*** | wg pubkey', }); }, generatePreSharedKey: () => { return exec('wg genpsk'); }, up: (infName: string) => { return exec(`wg-quick up ${infName}`); }, down: (infName: string) => { return exec(`wg-quick down ${infName}`); }, restart: (infName: string) => { return exec(`wg-quick down ${infName}; wg-quick up ${infName}`); }, sync: (infName: string) => { return exec(`wg syncconf ${infName} <(wg-quick strip ${infName})`); }, dump: async (infName: string) => { const rawDump = await exec(`wg show ${infName} dump`, { log: false, }); type wgDumpLine = [ string, string, string, string, string, string, string, string, ]; return rawDump .trim() .split('\n') .slice(1) .map((line) => { const splitLines = line.split('\t'); const [ publicKey, preSharedKey, endpoint, allowedIps, latestHandshakeAt, transferRx, transferTx, persistentKeepalive, ] = splitLines as wgDumpLine; return { publicKey, preSharedKey, endpoint: endpoint === '(none)' ? null : endpoint, allowedIps, latestHandshakeAt: latestHandshakeAt === '0' ? null : new Date(Number.parseInt(`${latestHandshakeAt}000`)), transferRx: Number.parseInt(transferRx), transferTx: Number.parseInt(transferTx), persistentKeepalive: persistentKeepalive, }; }); }, };