Files
wg-easy-ca-lose/src/server/utils/wgHelper.ts
T
Bernd Storath 8ea2b635c1 feat: change hooks to textareas (#2522)
* hooks are now textareas

* remove newlines in client config
2026-03-05 14:52:55 +01:00

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,
};
});
},
};