import type { BUN_SQLITE_WGUI_CLIENTS, BUN_SQLITE_WGUI_HOSTS, } from "@/db/types/db"; import { AppData } from "@/src/data/app-data"; import grabDirNames from "@/src/utils/grab-dir-names"; import { execSync } from "node:child_process"; import { readFileSync, writeFileSync } from "node:fs"; import path from "node:path"; import validateIpv4 from "@/src/utils/validate-ipv4"; import validateAllowedIps from "@/src/utils/validate-allowed-ips"; import validateEndpointHost from "@/src/utils/validate-endpoint-host"; import grabHostPublicIPAddress from "./grab-host-public-ip-address"; import setupWireguardHost from "./setup-wireguard-host"; import type { APIResponseObject } from "@moduletrace/bunext/types"; import BunSQLite from "@moduletrace/bun-sqlite"; import type { TableType, User } from "@/src/types"; import grabClientDirnames from "./grab-client-dir-names"; const { WIREGUARD_PUBLIC_KEY_FILE_NAME, WIREGUARD_CLIENT_CONFIG_FILE_NAME } = grabDirNames(); type Params = { client: BUN_SQLITE_WGUI_CLIENTS; host?: BUN_SQLITE_WGUI_HOSTS; user: User; /** * Skip the host setup/restart at the end. Use when * regenerating multiple clients and restarting the * host once afterwards instead. */ skip_host_setup?: boolean; }; /** * Function to setup a wireguard client. This * function will eventually call the setupWireguardHost * function afterwards to update it * @param param0 * @returns */ export default async function setupWireguardClient({ client, host, user, skip_host_setup, }: Params): Promise { const host_id = host?.id || AppData["WireguardMainHostID"]; let resolved_host = host; if (!resolved_host) { const host_res = await BunSQLite.select< BUN_SQLITE_WGUI_HOSTS, TableType >({ table: "hosts", targetId: host_id, }); resolved_host = host_res.singleRes || undefined; } const { CLIENT_WG_IP, CLIENT_CONFIG_FILE, CLIENT_DIR, CLIENT_PRIVATE_KEY_FILE, CLIENT_PUBLIC_KEY_FILE, HOST_CONFIG_DIR, } = grabClientDirnames({ client, host_id }); if (!CLIENT_WG_IP) { return { success: false, msg: `No Client Private IP address provided`, }; } if (!validateIpv4({ ip: CLIENT_WG_IP })) { return { success: false, msg: `Invalid Client Private IP address: ${CLIENT_WG_IP}`, }; } if ( client?.allow_all_ips != 1 && client?.allowed_ips && !validateAllowedIps({ value: client.allowed_ips }) ) { return { success: false, msg: `Invalid Allowed IPs: ${client.allowed_ips}`, }; } const HOST_WG_IP = resolved_host?.wg_ip_address; if (!HOST_WG_IP) { return { success: false, msg: `No Host Private IP address provided`, }; } let pre_sh = ``; pre_sh += `mkdir -p ${CLIENT_DIR}\n`; pre_sh += `if [ ! -f ${CLIENT_PRIVATE_KEY_FILE} ]; then\n`; pre_sh += ` wg genkey | tee ${CLIENT_PRIVATE_KEY_FILE} | wg pubkey > ${CLIENT_PUBLIC_KEY_FILE}\n`; pre_sh += `fi\n`; try { const exec_pre_setup = execSync(pre_sh, { encoding: "utf-8" }); } catch (error: any) { return { success: false, msg: error.message, }; } try { const PUBLIC_IP_ADDRESS = resolved_host?.public_ip_address || (await grabHostPublicIPAddress()) || client?.public_ip_address || HOST_WG_IP; if (!validateEndpointHost({ value: PUBLIC_IP_ADDRESS })) { return { success: false, msg: `Invalid Host public IP address: ${PUBLIC_IP_ADDRESS}`, }; } const LISTEN_PORT = Number( resolved_host?.listen_port || AppData["DefaultWGListenPort"], ); if (!Number.isInteger(LISTEN_PORT)) { return { success: false, msg: `Invalid Host listen port: ${resolved_host?.listen_port}`, }; } const CLIENT_PRIVATE_KEY = readFileSync( CLIENT_PRIVATE_KEY_FILE, "utf-8", ).trim(); const HOST_PUBLIC_KEY = readFileSync( path.join(HOST_CONFIG_DIR, WIREGUARD_PUBLIC_KEY_FILE_NAME), "utf-8", ).trim(); let CLIENT_CONFIG = ``; CLIENT_CONFIG += `\n`; CLIENT_CONFIG += `[Interface]\n`; CLIENT_CONFIG += `Address = ${CLIENT_WG_IP}/32\n`; CLIENT_CONFIG += `PrivateKey = ${CLIENT_PRIVATE_KEY}\n`; if (client?.allow_all_ips == 1) { CLIENT_CONFIG += `DNS = 1.1.1.1\n`; } CLIENT_CONFIG += `\n`; CLIENT_CONFIG += `[Peer]\n`; CLIENT_CONFIG += `PublicKey = ${HOST_PUBLIC_KEY}\n`; CLIENT_CONFIG += `Endpoint = ${PUBLIC_IP_ADDRESS}:${LISTEN_PORT}\n`; if (client?.allow_all_ips == 1) { CLIENT_CONFIG += `AllowedIPs = 0.0.0.0/0, ::/0\n`; } else if (client?.allowed_ips) { CLIENT_CONFIG += `AllowedIPs = ${client.allowed_ips}\n`; } CLIENT_CONFIG += `PersistentKeepalive = 25\n`; writeFileSync(CLIENT_CONFIG_FILE, CLIENT_CONFIG, { mode: 0o600 }); const host_setup_res = skip_host_setup ? { success: true } : await setupWireguardHost({ host: resolved_host, user, is_update_after_client_setup: true, }); if (!host_setup_res.success) { return { success: false, msg: `Client config created but Host update failed: ${host_setup_res.msg}`, }; } return { success: true, msg: `Client ${client.name} setup complete`, stringRes: CLIENT_CONFIG, }; } catch (error: any) { console.log( `Error setting up wireguard client ${client.id} for host ${host_id} =>`, error.message, ); return { success: false, msg: error.message, }; } }