8ea2b635c1
* hooks are now textareas * remove newlines in client config
256 lines
6.9 KiB
TypeScript
256 lines
6.9 KiB
TypeScript
// ! 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<ClientType, 'createdAt' | 'updatedAt'>,
|
|
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,
|
|
};
|
|
});
|
|
},
|
|
};
|