228 lines
6.9 KiB
TypeScript
228 lines
6.9 KiB
TypeScript
import type {
|
|
BUN_SQLITE_WGUI_CLIENTS,
|
|
BUN_SQLITE_WGUI_HOSTS,
|
|
BUN_SQLITE_WGUI_VARIABLES,
|
|
} from "@/db/types/db";
|
|
import { AppData } from "@/src/data/app-data";
|
|
import grabDirNames from "@/src/utils/grab-dir-names";
|
|
import deriveWireguardInterfaceName from "@/src/utils/derive-wireguard-interface-name";
|
|
import { execSync } from "node:child_process";
|
|
import path from "node:path";
|
|
import grabHostNetworkInterface from "./grab-host-network-interface";
|
|
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
|
import BunSQLite from "@moduletrace/bun-sqlite";
|
|
import type { TableType } from "@/src/types";
|
|
import checkPrivateIPAvailability from "./check-private-ip-availability";
|
|
|
|
const {
|
|
WGUI_LIB_IP_TABLES_DIR,
|
|
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
|
WGUI_WG_QUICK_SYSTEMD_SCRIPT,
|
|
WIREGUARD_PRIVATE_KEY_FILE_NAME,
|
|
WIREGUARD_PUBLIC_KEY_FILE_NAME,
|
|
} = grabDirNames();
|
|
|
|
type Params = {
|
|
host?: BUN_SQLITE_WGUI_HOSTS;
|
|
wg_subnet_ip?: string;
|
|
};
|
|
|
|
export default async function setupWireguardHost({
|
|
host,
|
|
wg_subnet_ip,
|
|
}: Params): Promise<APIResponseObject> {
|
|
const host_id = host?.id || AppData["WireguardHostID"];
|
|
|
|
const INTERFACE_NAME = deriveWireguardInterfaceName({ host_id });
|
|
const HOST_CONFIG_PATH = path.join(
|
|
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
|
`${INTERFACE_NAME}.conf`,
|
|
);
|
|
|
|
const variables_res = await BunSQLite.select<
|
|
BUN_SQLITE_WGUI_VARIABLES,
|
|
TableType
|
|
>({
|
|
table: "variables",
|
|
});
|
|
|
|
const variables = variables_res.payload;
|
|
|
|
const host_clients_res = await BunSQLite.select<
|
|
BUN_SQLITE_WGUI_CLIENTS,
|
|
TableType
|
|
>({
|
|
table: "clients",
|
|
query: {
|
|
query: {
|
|
host_id: {
|
|
value: host_id,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const clients = host_clients_res.payload || [];
|
|
|
|
const TARGET_INTERFACE = await grabHostNetworkInterface();
|
|
const HOST_WG_IP =
|
|
host?.wg_ip_address ||
|
|
variables?.find((v) => v.key == "main_host_wg_ip_address")?.value ||
|
|
wg_subnet_ip;
|
|
|
|
if (!HOST_WG_IP) {
|
|
return {
|
|
success: false,
|
|
msg: `No Host Private IP address provided`,
|
|
};
|
|
}
|
|
|
|
const is_ip_available = await checkPrivateIPAvailability({
|
|
ip_address: HOST_WG_IP,
|
|
});
|
|
|
|
if (!is_ip_available.success) {
|
|
return {
|
|
success: false,
|
|
msg: `IP not available`,
|
|
};
|
|
}
|
|
|
|
let pre_sh = ``;
|
|
|
|
pre_sh += `set -e\n`;
|
|
pre_sh += `mkdir -p ${WGUI_LIB_HOSTS_CONFIGS_DIR}\n`;
|
|
pre_sh += `cd ${WGUI_LIB_HOSTS_CONFIGS_DIR}\n`;
|
|
pre_sh += `if [ ! -f ${WIREGUARD_PRIVATE_KEY_FILE_NAME} ]; then\n`;
|
|
pre_sh += ` wg genkey | tee ${WIREGUARD_PRIVATE_KEY_FILE_NAME} | wg pubkey > ${WIREGUARD_PUBLIC_KEY_FILE_NAME}\n`;
|
|
pre_sh += `fi\n`;
|
|
|
|
try {
|
|
const exec_pre_setup = execSync(pre_sh, { encoding: "utf-8" });
|
|
|
|
console.log("exec_pre_setup", exec_pre_setup);
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
msg: error.message,
|
|
};
|
|
}
|
|
|
|
try {
|
|
const HOST_PUBLIC_KEY = host?.id
|
|
? host.public_key
|
|
: execSync(
|
|
`cat ${path.join(WGUI_LIB_HOSTS_CONFIGS_DIR, WIREGUARD_PUBLIC_KEY_FILE_NAME)}`,
|
|
);
|
|
const HOST_PRIVATE_KEY = host?.private_key
|
|
? host.private_key
|
|
: execSync(
|
|
`cat ${path.join(WGUI_LIB_HOSTS_CONFIGS_DIR, WIREGUARD_PRIVATE_KEY_FILE_NAME)}`,
|
|
);
|
|
|
|
let sh = ``;
|
|
|
|
sh += `set -e\n`;
|
|
|
|
const POST_UP_PATH = path.join(
|
|
WGUI_LIB_IP_TABLES_DIR,
|
|
`${host_id}-up.sh`,
|
|
);
|
|
const POST_DOWN_PATH = path.join(
|
|
WGUI_LIB_IP_TABLES_DIR,
|
|
`${host_id}-down.sh`,
|
|
);
|
|
|
|
sh += `cd ${WGUI_LIB_HOSTS_CONFIGS_DIR}\n`;
|
|
|
|
sh += `cat > ${POST_UP_PATH} << EOF\n`;
|
|
sh += `#!/bin/bash\n\n`;
|
|
sh += `# Allow WireGuard traffic to/from the server itself\n`;
|
|
sh += `iptables -I INPUT 1 -i ${INTERFACE_NAME} -j ACCEPT\n`;
|
|
sh += `iptables -I OUTPUT 1 -o ${INTERFACE_NAME} -j ACCEPT\n`;
|
|
sh += `\n`;
|
|
sh += `# Allow WireGuard traffic to be forwarded (insert above Docker rules)\n`;
|
|
sh += `iptables -I FORWARD 1 -i ${INTERFACE_NAME} -j ACCEPT\n`;
|
|
sh += `iptables -I FORWARD 1 -o ${INTERFACE_NAME} -j ACCEPT\n`;
|
|
sh += `\n`;
|
|
sh += `iptables -t nat -A POSTROUTING -o ${TARGET_INTERFACE} -j MASQUERADE\n`;
|
|
sh += `EOF\n`;
|
|
|
|
sh += `\n`;
|
|
|
|
sh += `cat > ${POST_DOWN_PATH} << EOF\n`;
|
|
sh += `#!/bin/bash\n\n`;
|
|
sh += `# Remove WireGuard INPUT/OUTPUT rules\n`;
|
|
sh += `iptables -D INPUT -i ${INTERFACE_NAME} -j ACCEPT\n`;
|
|
sh += `iptables -D OUTPUT -o ${INTERFACE_NAME} -j ACCEPT\n`;
|
|
sh += `\n`;
|
|
sh += `# Remove FORWARD rules\n`;
|
|
sh += `iptables -D FORWARD -i ${INTERFACE_NAME} -j ACCEPT\n`;
|
|
sh += `iptables -D FORWARD -o ${INTERFACE_NAME} -j ACCEPT\n`;
|
|
sh += `\n`;
|
|
sh += `iptables -t nat -D POSTROUTING -o ${TARGET_INTERFACE} -j MASQUERADE\n`;
|
|
sh += `EOF\n`;
|
|
|
|
sh += `\n`;
|
|
|
|
sh += `cat > ${INTERFACE_NAME}.conf << EOF\n`;
|
|
sh += `[Interface]\n`;
|
|
sh += `Address = ${HOST_WG_IP}/24\n`;
|
|
sh += `ListenPort = 51820\n`;
|
|
sh += `PrivateKey = ${HOST_PRIVATE_KEY}\n`;
|
|
sh += `PostUp = ${POST_UP_PATH}\n`;
|
|
sh += `PostDown = ${POST_DOWN_PATH}\n`;
|
|
sh += `\n`;
|
|
|
|
if (clients[0]) {
|
|
for (let i = 0; i < clients.length; i++) {
|
|
const client = clients[i];
|
|
if (!client?.id || !client.public_key) continue;
|
|
|
|
sh += `[Peer]\n`;
|
|
sh += `PublicKey = ${client.public_key}\n`;
|
|
sh += `AllowedIPs = ${client.allowed_ips}\n`;
|
|
sh += `\n`;
|
|
}
|
|
}
|
|
|
|
sh += `EOF\n`;
|
|
|
|
sh += `\n`;
|
|
|
|
const exec = execSync(sh, { encoding: "utf-8" });
|
|
|
|
const IS_ROOT =
|
|
typeof process.getuid === "function" && process.getuid() === 0;
|
|
const SUDO_PREFIX = IS_ROOT ? "" : "sudo -n ";
|
|
const MANAGE_WG_QUICK_CMD = `${SUDO_PREFIX}${WGUI_WG_QUICK_SYSTEMD_SCRIPT} ${INTERFACE_NAME} ${HOST_CONFIG_PATH}`;
|
|
|
|
let exec_systemd = ``;
|
|
|
|
try {
|
|
exec_systemd = execSync(MANAGE_WG_QUICK_CMD, {
|
|
encoding: "utf-8",
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
msg: [exec.trim(), exec_systemd.trim()].join("\n\n"),
|
|
};
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
msg: `Host config written to ${HOST_CONFIG_PATH}, but could not manage the tunnel via systemd (wg-quick@${INTERFACE_NAME}.service): ${error.message}`,
|
|
};
|
|
}
|
|
} catch (error: any) {
|
|
return {
|
|
success: false,
|
|
msg: error.message,
|
|
};
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
};
|
|
}
|