From 44b3412179adbfe06db69ed1a7f19f042d23db7c Mon Sep 17 00:00:00 2001 From: ne0x Date: Tue, 27 Jan 2026 05:45:31 +0400 Subject: [PATCH] - Add AmneziaWG 2.0 support; - buildAmneziaQrPack is async (zlib deflate); - ClientQrSchema now has 'wireguard' as fallback --- .../database/repositories/client/types.ts | 7 +++-- src/server/utils/WireGuard.ts | 9 ++++--- src/server/utils/wgHelper.ts | 26 +++++++++++-------- 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/server/database/repositories/client/types.ts b/src/server/database/repositories/client/types.ts index 266af0f2..965800a5 100644 --- a/src/server/database/repositories/client/types.ts +++ b/src/server/database/repositories/client/types.ts @@ -104,11 +104,10 @@ export type ClientCreateFromExistingType = Pick< | 'enabled' >; -const qrType = z.preprocess( - (val) => (val === 'amnezia-vpn' ? val : undefined), - z.enum(['amnezia-vpn']).optional() -); +const qrType = z.enum(['amnezia-vpn', 'wireguard']).catch('wireguard'); export const ClientQrSchema = z.object({ type: qrType, }); + +export type QrType = z.infer; diff --git a/src/server/utils/WireGuard.ts b/src/server/utils/WireGuard.ts index e0644844..08646326 100644 --- a/src/server/utils/WireGuard.ts +++ b/src/server/utils/WireGuard.ts @@ -2,6 +2,7 @@ import fs from 'node:fs/promises'; import debug from 'debug'; import { encodeQR } from 'qr'; import type { InterfaceType } from '#db/repositories/interface/types'; +import type { QrType } from '#db/repositories/client/types'; const WG_DEBUG = debug('WireGuard'); @@ -149,7 +150,7 @@ class WireGuard { return clients; } - async getClientConfiguration({ clientId, type }: { clientId: ID, type?: 'amnezia-vpn' }) { + async getClientConfiguration({ clientId, type }: { clientId: ID, type?: QrType }) { const wgInterface = await Database.interfaces.get(); const userConfig = await Database.userConfigs.get(); @@ -167,13 +168,15 @@ class WireGuard { const amneziaVPNClientConfig = wg.generateAmneziaVPNClientConfig(wgInterface, userConfig, client, configText, { enableIpv6: !WG_ENV.DISABLE_IPV6, }); - return wg.buildAmneziaQrPack(JSON.stringify(amneziaVPNClientConfig)); + const qrConfigPack = await wg.buildAmneziaQrPack(JSON.stringify(amneziaVPNClientConfig)); + + return qrConfigPack; } return configText; } - async getClientQRCodeSVG({ clientId, type }: { clientId: ID, type?: 'amnezia-vpn' }) { + async getClientQRCodeSVG({ clientId, type }: { clientId: ID, type?: QrType }) { const config = await this.getClientConfiguration({ clientId, type }); const ECMode = ['high', 'quartile', 'medium', 'low'] as const; for (const ecc of ECMode) { diff --git a/src/server/utils/wgHelper.ts b/src/server/utils/wgHelper.ts index 22dc9a1d..762fcab7 100644 --- a/src/server/utils/wgHelper.ts +++ b/src/server/utils/wgHelper.ts @@ -1,4 +1,5 @@ -import { deflateSync as zlibDeflateSync } from 'node:zlib'; +import { deflate } from 'node:zlib'; +import { promisify } from 'node:util'; import { parseCidr } from 'cidr-tools'; import { stringifyIp } from 'ip-bigint'; import type { ClientType } from '#db/repositories/client/types'; @@ -6,6 +7,8 @@ import type { InterfaceType } from '#db/repositories/interface/types'; import type { UserConfigType } from '#db/repositories/userConfig/types'; import type { HooksType } from '#db/repositories/hooks/types'; +const zlibDeflate = promisify(deflate); + type Options = { enableIpv6?: boolean; }; @@ -188,20 +191,14 @@ Endpoint = ${userConfig.host}:${userConfig.port}`; if (wgExecutable === 'awg') { containerType = 'awg'; - // S3, S4, i1, i2, i3, i4, i5 are not supported by AmneziaVPN app for now const awgParams = { Jc: client.jC, Jmin: client.jMin, Jmax: client.jMax, S1: wgInterface.s1, S2: wgInterface.s2, - // S3: wgInterface.s3, - // S4: wgInterface.s4, - // i1: client.i1, - // i2: client.i2, - // i3: client.i3, - // i4: client.i4, - // i5: client.i5, + S3: wgInterface.s3, + S4: wgInterface.s4, H1: wgInterface.h1, H2: wgInterface.h2, H3: wgInterface.h3, @@ -217,6 +214,12 @@ Endpoint = ${userConfig.host}:${userConfig.port}`; ); } + const protocolInfo: { protocol_version?: string } = {}; + + if (awgExtras.hasOwnProperty('S3') && awgExtras.hasOwnProperty('S4')) { + protocolInfo.protocol_version = '2'; + } + const lastConfigObj = { ...awgExtras, allowed_ips: client.allowedIps ?? userConfig.defaultAllowedIps ?? [], @@ -238,6 +241,7 @@ Endpoint = ${userConfig.host}:${userConfig.port}`; isThirdPartyConfig: true, last_config: JSON.stringify(lastConfigObj), port: `${userConfig.port}`, + ...protocolInfo, transport_proto: 'udp', }, container: `amnezia-${containerType}`, @@ -251,14 +255,14 @@ Endpoint = ${userConfig.host}:${userConfig.port}`; }; }, - buildAmneziaQrPack: (amneziaConfigJSON: string) => { + buildAmneziaQrPack: async (amneziaConfigJSON: string) => { // Observed working QR wrapper: // [0..3] magic/version (0x07C00100) // [4..7] zlib_len + 4 // [8..11] uncompressed_len // [12..] zlib(deflate) bytes (starts with 78 DA typically) const plain = Buffer.from(amneziaConfigJSON, 'utf8'); - const z = zlibDeflateSync(plain); + const z = await zlibDeflate(plain); const MAGIC = 0x07c00100; const header = Buffer.allocUnsafe(12);