Major updates
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
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 type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
import { execSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
|
||||
const { WGUI_LIB_HOSTS_CONFIGS_DIR, WGUI_WG_QUICK_MANAGE_SCRIPT } =
|
||||
grabDirNames();
|
||||
|
||||
export type WireguardHostAction = "up" | "down" | "restart";
|
||||
|
||||
type Params = {
|
||||
action: WireguardHostAction;
|
||||
host_id?: number;
|
||||
};
|
||||
|
||||
export default function manageWireguardHost({
|
||||
action,
|
||||
host_id = AppData["WireguardHostID"],
|
||||
}: Params): APIResponseObject {
|
||||
const INTERFACE_NAME = deriveWireguardInterfaceName({ host_id });
|
||||
const HOST_CONFIG_PATH = path.join(
|
||||
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
||||
`${INTERFACE_NAME}.conf`,
|
||||
);
|
||||
|
||||
const IS_ROOT =
|
||||
typeof process.getuid === "function" && process.getuid() === 0;
|
||||
|
||||
if (!IS_ROOT) {
|
||||
return {
|
||||
success: false,
|
||||
msg: "wg-ui must run as root to manage wireguard tunnels",
|
||||
};
|
||||
}
|
||||
|
||||
const MANAGE_COMMAND = `${WGUI_WG_QUICK_MANAGE_SCRIPT} ${action} ${INTERFACE_NAME} ${HOST_CONFIG_PATH}`;
|
||||
|
||||
try {
|
||||
const output = execSync(MANAGE_COMMAND, { encoding: "utf-8" }).trim();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
msg: output,
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
msg: `Could not ${action} ${INTERFACE_NAME}: ${error.message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -11,20 +11,20 @@ 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 } from "@/src/types";
|
||||
import type { TableType, User } from "@/src/types";
|
||||
|
||||
const {
|
||||
WGUI_LIB_CLIENTS_CONFIGS_DIR,
|
||||
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
||||
WIREGUARD_PRIVATE_KEY_FILE_NAME,
|
||||
WIREGUARD_PUBLIC_KEY_FILE_NAME,
|
||||
WGUI_LIB_KEYS_DIR,
|
||||
WGUI_LIB_HOST_CLIENTS_DIR_NAME,
|
||||
WIREGUARD_CLIENT_CONFIG_FILE_NAME,
|
||||
} = grabDirNames();
|
||||
|
||||
type Params = {
|
||||
client: BUN_SQLITE_WGUI_CLIENTS;
|
||||
host?: BUN_SQLITE_WGUI_HOSTS;
|
||||
user: User;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -37,6 +37,7 @@ type Params = {
|
||||
export default async function setupWireguardClient({
|
||||
client,
|
||||
host,
|
||||
user,
|
||||
}: Params): Promise<APIResponseObject> {
|
||||
const host_id = host?.id || AppData["WireguardHostID"];
|
||||
|
||||
@@ -58,9 +59,21 @@ export default async function setupWireguardClient({
|
||||
};
|
||||
}
|
||||
|
||||
const HOST_WG_IP =
|
||||
host?.wg_ip_address ||
|
||||
variables?.find((v) => v.key == "main_host_wg_ip_address")?.value;
|
||||
const HOST_CONFIG_DIR = path.join(
|
||||
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
||||
String(host_id),
|
||||
);
|
||||
|
||||
const HOST_CLIENTS_DIR = path.join(
|
||||
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
||||
WGUI_LIB_HOST_CLIENTS_DIR_NAME,
|
||||
);
|
||||
|
||||
const CLIENT_DIR = path.join(HOST_CLIENTS_DIR, `${client.id}`);
|
||||
|
||||
const HOST_WG_IP = host?.id
|
||||
? host?.wg_ip_address
|
||||
: variables?.find((v) => v.key == "main_host_wg_ip_address")?.value;
|
||||
|
||||
if (!HOST_WG_IP) {
|
||||
return {
|
||||
@@ -69,8 +82,6 @@ export default async function setupWireguardClient({
|
||||
};
|
||||
}
|
||||
|
||||
const CLIENT_DIR = path.join(WGUI_LIB_CLIENTS_CONFIGS_DIR, `${client.id}`);
|
||||
|
||||
const CLIENT_PRIVATE_KEY_FILE = path.join(
|
||||
CLIENT_DIR,
|
||||
WIREGUARD_PRIVATE_KEY_FILE_NAME,
|
||||
@@ -101,66 +112,44 @@ export default async function setupWireguardClient({
|
||||
}
|
||||
|
||||
try {
|
||||
const CLIENT_PRIVATE_KEY =
|
||||
client?.private_key ||
|
||||
execSync(`cat ${CLIENT_PRIVATE_KEY_FILE}`, {
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
const CLIENT_PRIVATE_KEY = execSync(`cat ${CLIENT_PRIVATE_KEY_FILE}`, {
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
if (CLIENT_PRIVATE_KEY && !client.private_key && client.id) {
|
||||
await BunSQLite.update<BUN_SQLITE_WGUI_CLIENTS, TableType>({
|
||||
data: { private_key: CLIENT_PRIVATE_KEY },
|
||||
table: "clients",
|
||||
targetId: client.id,
|
||||
});
|
||||
}
|
||||
const CLIENT_PUBLIC_KEY = execSync(`cat ${CLIENT_PUBLIC_KEY_FILE}`, {
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
const CLIENT_PUBLIC_KEY =
|
||||
client?.public_key ||
|
||||
execSync(`cat ${CLIENT_PUBLIC_KEY_FILE}`, {
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
|
||||
if (CLIENT_PUBLIC_KEY && !client.public_key && client.id) {
|
||||
await BunSQLite.update<BUN_SQLITE_WGUI_CLIENTS, TableType>({
|
||||
data: { public_key: CLIENT_PUBLIC_KEY },
|
||||
table: "clients",
|
||||
targetId: client.id,
|
||||
});
|
||||
}
|
||||
|
||||
const HOST_PUBLIC_KEY =
|
||||
host?.public_key ||
|
||||
execSync(
|
||||
`cat ${path.join(
|
||||
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
||||
WIREGUARD_PUBLIC_KEY_FILE_NAME,
|
||||
)}`,
|
||||
{ encoding: "utf-8" },
|
||||
).trim();
|
||||
|
||||
const ALLOWED_IPS = client?.allowed_ips || `0.0.0.0/0`;
|
||||
const HOST_PUBLIC_KEY = execSync(
|
||||
`cat ${path.join(HOST_CONFIG_DIR, WIREGUARD_PUBLIC_KEY_FILE_NAME)}`,
|
||||
{ encoding: "utf-8" },
|
||||
).trim();
|
||||
|
||||
const PUBLIC_IP_ADDRESS =
|
||||
(await grabHostPublicIPAddress()) ||
|
||||
client?.public_ip_address ||
|
||||
HOST_WG_IP;
|
||||
|
||||
const sh = `
|
||||
cd ${CLIENT_DIR}
|
||||
let sh = ``;
|
||||
|
||||
cat > ${WIREGUARD_CLIENT_CONFIG_FILE_NAME} << EOF
|
||||
[Interface]
|
||||
Address = ${CLIENT_WG_IP}/32
|
||||
PrivateKey = ${CLIENT_PRIVATE_KEY}
|
||||
DNS = 1.1.1.1
|
||||
sh += `cd ${CLIENT_DIR}\n`;
|
||||
sh += `cat > ${WIREGUARD_CLIENT_CONFIG_FILE_NAME} << EOF\n`;
|
||||
sh += `\n`;
|
||||
sh += `[Interface]\n`;
|
||||
sh += `Address = ${CLIENT_WG_IP}/32\n`;
|
||||
sh += `PrivateKey = ${CLIENT_PRIVATE_KEY}\n`;
|
||||
sh += `DNS = 1.1.1.1\n`;
|
||||
sh += `\n`;
|
||||
sh += `[Peer]\n`;
|
||||
sh += `PublicKey = ${HOST_PUBLIC_KEY}\n`;
|
||||
sh += `Endpoint = ${PUBLIC_IP_ADDRESS}:51820\n`;
|
||||
|
||||
[Peer]
|
||||
PublicKey = ${HOST_PUBLIC_KEY}
|
||||
Endpoint = ${PUBLIC_IP_ADDRESS}:51820
|
||||
AllowedIPs = ${ALLOWED_IPS}
|
||||
EOF
|
||||
`;
|
||||
if (client?.allowed_ips) {
|
||||
sh += `AllowedIPs = ${client.allowed_ips}\n`;
|
||||
}
|
||||
|
||||
sh += `EOF\n`;
|
||||
sh += `\n`;
|
||||
|
||||
const exec = execSync(sh, { encoding: "utf-8" });
|
||||
|
||||
@@ -168,7 +157,7 @@ EOF
|
||||
encoding: "utf-8",
|
||||
});
|
||||
|
||||
const host_setup_res = await setupWireguardHost({ host });
|
||||
const host_setup_res = await setupWireguardHost({ host, user });
|
||||
|
||||
if (!host_setup_res.success) {
|
||||
return {
|
||||
|
||||
@@ -11,31 +11,50 @@ 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 type { TableType, User } from "@/src/types";
|
||||
import checkPrivateIPAvailability from "./check-private-ip-availability";
|
||||
import manageWireguardHost from "./manage-wireguard-host";
|
||||
|
||||
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,
|
||||
WGUI_LIB_HOST_CLIENTS_DIR_NAME,
|
||||
WGUI_LIB_HOST_IPTABLES_DIR_NAME,
|
||||
} = grabDirNames();
|
||||
|
||||
type Params = {
|
||||
host?: BUN_SQLITE_WGUI_HOSTS;
|
||||
wg_subnet_ip?: string;
|
||||
user: User;
|
||||
};
|
||||
|
||||
export default async function setupWireguardHost({
|
||||
host,
|
||||
wg_subnet_ip,
|
||||
user,
|
||||
}: Params): Promise<APIResponseObject> {
|
||||
const host_id = host?.id || AppData["WireguardHostID"];
|
||||
|
||||
const INTERFACE_NAME = deriveWireguardInterfaceName({ host_id });
|
||||
const HOST_CONFIG_PATH = path.join(
|
||||
|
||||
const HOST_CONFIG_DIR = path.join(
|
||||
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
||||
String(host_id),
|
||||
);
|
||||
|
||||
const HOST_CLIENTS_DIR = path.join(
|
||||
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
||||
WGUI_LIB_HOST_CLIENTS_DIR_NAME,
|
||||
);
|
||||
|
||||
const HOST_IPTABLES_DIR = path.join(
|
||||
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
||||
WGUI_LIB_HOST_IPTABLES_DIR_NAME,
|
||||
);
|
||||
|
||||
const HOST_CONFIG_FILE = path.join(
|
||||
HOST_CONFIG_DIR,
|
||||
`${INTERFACE_NAME}.conf`,
|
||||
);
|
||||
|
||||
@@ -91,16 +110,14 @@ export default async function setupWireguardHost({
|
||||
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 += `mkdir -p ${HOST_CONFIG_DIR}\n`;
|
||||
pre_sh += `cd ${HOST_CONFIG_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,
|
||||
@@ -109,31 +126,66 @@ export default async function setupWireguardHost({
|
||||
}
|
||||
|
||||
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)}`,
|
||||
);
|
||||
const HOST_PUBLIC_KEY = execSync(
|
||||
`cat ${path.join(HOST_CONFIG_DIR, WIREGUARD_PUBLIC_KEY_FILE_NAME)}`,
|
||||
{ encoding: "utf-8" },
|
||||
).trim();
|
||||
|
||||
const HOST_PRIVATE_KEY = execSync(
|
||||
`cat ${path.join(HOST_CONFIG_DIR, WIREGUARD_PRIVATE_KEY_FILE_NAME)}`,
|
||||
{ encoding: "utf-8" },
|
||||
).trim();
|
||||
|
||||
if (host_id == 0) {
|
||||
const update_variables = await BunSQLite.insert<
|
||||
BUN_SQLITE_WGUI_VARIABLES,
|
||||
TableType
|
||||
>({
|
||||
table: "variables",
|
||||
data: [
|
||||
{
|
||||
key: "main_host_wg_ip_address",
|
||||
value: HOST_WG_IP,
|
||||
},
|
||||
{
|
||||
key: "main_host_wg_public_key",
|
||||
value: HOST_PUBLIC_KEY,
|
||||
},
|
||||
],
|
||||
update_on_duplicate: true,
|
||||
});
|
||||
|
||||
console.log("update_variables", update_variables);
|
||||
|
||||
if (!update_variables.success) {
|
||||
throw new Error(`Couldn't update host variables`);
|
||||
}
|
||||
} else if (host_id) {
|
||||
const update_host = await BunSQLite.insert<
|
||||
BUN_SQLITE_WGUI_HOSTS,
|
||||
TableType
|
||||
>({
|
||||
table: "hosts",
|
||||
data: [
|
||||
{
|
||||
id: host_id,
|
||||
user_id: user.id,
|
||||
wg_ip_address: HOST_WG_IP,
|
||||
public_key: HOST_PUBLIC_KEY,
|
||||
},
|
||||
],
|
||||
update_on_duplicate: true,
|
||||
});
|
||||
}
|
||||
|
||||
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`,
|
||||
);
|
||||
const POST_UP_PATH = path.join(HOST_IPTABLES_DIR, `up.sh`);
|
||||
const POST_DOWN_PATH = path.join(HOST_IPTABLES_DIR, `down.sh`);
|
||||
|
||||
sh += `cd ${WGUI_LIB_HOSTS_CONFIGS_DIR}\n`;
|
||||
sh += `cd ${HOST_CONFIG_DIR}\n`;
|
||||
|
||||
sh += `cat > ${POST_UP_PATH} << EOF\n`;
|
||||
sh += `#!/bin/bash\n\n`;
|
||||
@@ -147,6 +199,7 @@ export default async function setupWireguardHost({
|
||||
sh += `\n`;
|
||||
sh += `iptables -t nat -A POSTROUTING -o ${TARGET_INTERFACE} -j MASQUERADE\n`;
|
||||
sh += `EOF\n`;
|
||||
sh += `chmod +x ${POST_UP_PATH}\n`;
|
||||
|
||||
sh += `\n`;
|
||||
|
||||
@@ -162,6 +215,7 @@ export default async function setupWireguardHost({
|
||||
sh += `\n`;
|
||||
sh += `iptables -t nat -D POSTROUTING -o ${TARGET_INTERFACE} -j MASQUERADE\n`;
|
||||
sh += `EOF\n`;
|
||||
sh += `chmod +x ${POST_DOWN_PATH}\n`;
|
||||
|
||||
sh += `\n`;
|
||||
|
||||
@@ -192,36 +246,26 @@ export default async function setupWireguardHost({
|
||||
|
||||
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}`;
|
||||
const manage_res = manageWireguardHost({
|
||||
host_id,
|
||||
action: "restart",
|
||||
});
|
||||
|
||||
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) {
|
||||
if (!manage_res.success) {
|
||||
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}`,
|
||||
msg: `Host config written to ${HOST_CONFIG_FILE}, but the tunnel could not be restarted: ${manage_res.msg}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
msg: [exec.trim(), manage_res.msg].join("\n\n"),
|
||||
};
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
msg: error.message,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import grabDirNames from "@/src/utils/grab-dir-names";
|
||||
import fs from "node:fs";
|
||||
import manageWireguardHost from "./manage-wireguard-host";
|
||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||
import type {
|
||||
BUN_SQLITE_WGUI_HOSTS,
|
||||
BUN_SQLITE_WGUI_VARIABLES,
|
||||
} from "@/db/types/db";
|
||||
import type { TableType } from "@/src/types";
|
||||
import { AppData } from "@/src/data/app-data";
|
||||
|
||||
const HOST_CONFIG_FILE_NAME_PATTERN = /^wgui(\d+)\.conf$/;
|
||||
|
||||
export default async function syncWireguardHosts() {
|
||||
const { WGUI_LIB_HOSTS_CONFIGS_DIR } = grabDirNames();
|
||||
|
||||
let host_config_file_names: string[] = [];
|
||||
|
||||
try {
|
||||
host_config_file_names = fs
|
||||
.readdirSync(WGUI_LIB_HOSTS_CONFIGS_DIR)
|
||||
.filter((file_name) =>
|
||||
HOST_CONFIG_FILE_NAME_PATTERN.test(file_name),
|
||||
);
|
||||
|
||||
const variables = await BunSQLite.select<
|
||||
BUN_SQLITE_WGUI_VARIABLES,
|
||||
TableType
|
||||
>({
|
||||
table: "variables",
|
||||
});
|
||||
|
||||
const hosts = await BunSQLite.select<BUN_SQLITE_WGUI_HOSTS, TableType>({
|
||||
table: "hosts",
|
||||
});
|
||||
|
||||
const main_host_id = AppData["WireguardHostID"];
|
||||
const main_host_ip = variables.payload?.find(
|
||||
(v) => v.key == "main_host_wg_ip_address",
|
||||
)?.value;
|
||||
|
||||
if (!main_host_ip) {
|
||||
throw new Error(`Main Host not set yet`);
|
||||
}
|
||||
|
||||
for (const host_config_file_name of host_config_file_names) {
|
||||
const host_id = Number(
|
||||
host_config_file_name.match(HOST_CONFIG_FILE_NAME_PATTERN)?.[1],
|
||||
);
|
||||
|
||||
const res = manageWireguardHost({ action: "up", host_id });
|
||||
|
||||
console.log(
|
||||
`[wgui] wireguard host ${host_config_file_name}: ${
|
||||
res.success ? `up` : `failed — ${res.msg}`
|
||||
}`,
|
||||
);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.log(`[wgui] skipping wireguard host sync — ${error.message}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user