// ! Auto Imports are not supported in this file import { parseCidr } from 'cidr-tools'; import { stringifyIp } from 'ip-bigint'; import { removeNewlines } from './template'; 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'; type Options = { enableIpv6?: boolean; }; // needed to support cli const wgExecutable = typeof WG_ENV !== 'undefined' ? WG_ENV.WG_EXECUTABLE : 'dev'; export const wg = { generateServerPeer: ( client: Omit, options: Options = {} ) => { const { enableIpv6 = true } = options; const allowedIps = [ `${client.ipv4Address}/32`, ...(enableIpv6 ? [`${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, options: Options = {} ) => { const { enableIpv6 = true } = options; 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 }); const address = `${ipv4Addr}/${cidr4.prefix}` + (enableIpv6 ? `, ${ipv6Addr}/${cidr6.prefix}` : ''); let awgLines: string[] = []; if (wgExecutable === 'awg') { const parameters = { Jc: wgInterface.jC, Jmin: wgInterface.jMin, Jmax: wgInterface.jMax, S1: wgInterface.s1, S2: wgInterface.s2, S3: wgInterface.s3, S4: wgInterface.s4, H1: wgInterface.h1, H2: wgInterface.h2, H3: wgInterface.h3, H4: wgInterface.h4, I1: wgInterface.i1, I2: wgInterface.i2, I3: wgInterface.i3, I4: wgInterface.i4, I5: wgInterface.i5, } as const; awgLines = Object.entries(parameters) .filter(([_, value]) => !!value) .map(([key, value]) => `${key} = ${value}`); } const extraLines = [...awgLines].filter((v) => v !== null); return `# Note: Do not edit this file directly. # Your changes will be overwritten! # Server [Interface] PrivateKey = ${wgInterface.privateKey} Address = ${address} ListenPort = ${wgInterface.port} MTU = ${wgInterface.mtu} ${extraLines.length ? `${extraLines.join('\n')}\n` : ''} 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, options: Options = {} ) => { const { enableIpv6 = true } = options; const address = `${client.ipv4Address}/32` + (enableIpv6 ? `, ${client.ipv6Address}/128` : ''); const hookLines = [ client.preUp ? `PreUp = ${removeNewlines(client.preUp)}` : null, client.postUp ? `PostUp = ${removeNewlines(client.postUp)}` : null, client.preDown ? `PreDown = ${removeNewlines(client.preDown)}` : null, client.postDown ? `PostDown = ${removeNewlines(client.postDown)}` : null, ]; const dnsServers = client.dns ?? userConfig.defaultDns; const dnsLine = dnsServers.length > 0 ? `DNS = ${dnsServers.join(', ')}` : null; let awgLines: string[] = []; if (wgExecutable === 'awg') { const parameters = { Jc: client.jC, Jmin: client.jMin, Jmax: client.jMax, S1: wgInterface.s1, S2: wgInterface.s2, S3: wgInterface.s3, S4: wgInterface.s4, H1: wgInterface.h1, H2: wgInterface.h2, H3: wgInterface.h3, H4: wgInterface.h4, I1: client.i1, I2: client.i2, I3: client.i3, I4: client.i4, I5: client.i5, } as const; awgLines = Object.entries(parameters) .filter(([_, value]) => !!value) .map(([key, value]) => `${key} = ${value}`); } const extraLines = [dnsLine, ...hookLines, ...awgLines].filter( (v) => v !== null ); return `[Interface] PrivateKey = ${client.privateKey} Address = ${address} MTU = ${client.mtu} ${extraLines.length ? `${extraLines.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(`${wgExecutable} genkey`); }, getPublicKey: (privateKey: string) => { return exec(`echo ${privateKey} | ${wgExecutable} pubkey`, { log: `echo ***hidden*** | ${wgExecutable} pubkey`, }); }, generatePreSharedKey: () => { return exec(`${wgExecutable} genpsk`); }, up: (infName: string) => { return exec(`${wgExecutable}-quick up ${infName}`); }, down: (infName: string) => { return exec(`${wgExecutable}-quick down ${infName}`); }, restart: (infName: string) => { return exec( `${wgExecutable}-quick down ${infName}; ${wgExecutable}-quick up ${infName}` ); }, sync: (infName: string) => { return exec( `${wgExecutable} syncconf ${infName} <(${wgExecutable}-quick strip ${infName})` ); }, dump: async (infName: string) => { const rawDump = await exec(`${wgExecutable} 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, }; }); }, };