diff --git a/db/schema.ts b/db/schema.ts index c5ba6a3..2be45c1 100644 --- a/db/schema.ts +++ b/db/schema.ts @@ -1,3 +1,4 @@ +import { AppData } from "@/src/data/app-data"; import { ClientRuleProtocols, ClientRuleTypes, @@ -231,6 +232,11 @@ const schema: BUN_SQLITE_DatabaseSchemaType = { fieldName: "public_ip_address", dataType: "TEXT", }, + { + fieldName: "listen_port", + dataType: "INTEGER", + defaultValue: AppData["DefaultWGListenPort"], + }, { fieldName: "interface", dataType: "TEXT", diff --git a/db/types/db.ts b/db/types/db.ts index 77818a6..3e9433d 100644 --- a/db/types/db.ts +++ b/db/types/db.ts @@ -134,6 +134,7 @@ export type BUN_SQLITE_WGUI_HOSTS = { name?: string; short_description?: string; public_ip_address?: string; + listen_port?: number | ""; interface?: string; wg_ip_address?: string; public_key?: string; diff --git a/src/components/general/setup-main-host-button.tsx b/src/components/general/setup-main-host-button.tsx index 700fe67..72f0349 100755 --- a/src/components/general/setup-main-host-button.tsx +++ b/src/components/general/setup-main-host-button.tsx @@ -5,17 +5,12 @@ import fetchApi from "../twui/utils/fetch/fetchApi"; import type { ApiReqParams } from "@/src/types"; import Row from "../twui/layout/Row"; import Stack from "../twui/layout/Stack"; -import { - CircleCheck, - TriangleAlert, -} from "lucide-react"; +import { CircleCheck, TriangleAlert } from "lucide-react"; import Span from "../twui/layout/Span"; import Tag from "../twui/elements/Tag"; import { AppData } from "@/src/data/app-data"; import type { APIResponseObject } from "@moduletrace/bunext/types"; -import HostWgIpField, { - type HostWgIpFieldStatus, -} from "./host-wg-ip-field"; +import HostWgIpField, { type HostWgIpFieldStatus } from "./host-wg-ip-field"; import GrabNextSubnetButton from "./grab-next-subnet-button"; type Props = { @@ -30,12 +25,12 @@ export default function SetupMainHostButton({ button_props }: Props) { return ( - + /> */} {status?.error && status.msg ? ( ); -} \ No newline at end of file +} diff --git a/src/functions/backend/setup/create-wireguard-host.ts b/src/functions/backend/setup/create-wireguard-host.ts index 9d8decc..fdb395d 100644 --- a/src/functions/backend/setup/create-wireguard-host.ts +++ b/src/functions/backend/setup/create-wireguard-host.ts @@ -1,4 +1,7 @@ -import type { BUN_SQLITE_WGUI_HOSTS, BUN_SQLITE_WGUI_VARIABLES } from "@/db/types/db"; +import type { + BUN_SQLITE_WGUI_HOSTS, + BUN_SQLITE_WGUI_VARIABLES, +} from "@/db/types/db"; import BunSQLite from "@moduletrace/bun-sqlite"; import type { APIResponseObject } from "@moduletrace/bunext/types"; import type { TableType, User } from "@/src/types"; @@ -9,7 +12,11 @@ type Params = { wg_ip_address?: string | null; public_ip_address?: string | null; interface?: string | null; + name?: string | null; + short_description?: string | null; + listen_port?: number | null; user: User; + is_main_host?: boolean; }; /** @@ -24,7 +31,11 @@ export default async function createWireguardHost({ wg_ip_address, public_ip_address, interface: iface, + name, + short_description, + listen_port, user, + is_main_host, }: Params): Promise { const wg_ip = (wg_ip_address || "").trim(); @@ -38,24 +49,34 @@ export default async function createWireguardHost({ const detected_public_ip = await grabHostPublicIPAddress(); const final_public_ip = public_ip_address?.trim() || detected_public_ip; const final_interface = iface?.trim() || undefined; + const final_name = name?.trim() || undefined; + const final_short_description = short_description?.trim() || undefined; try { + const insert_data: BUN_SQLITE_WGUI_HOSTS = { + user_id: user.id, + wg_ip_address: wg_ip, + public_ip_address: final_public_ip || undefined, + interface: final_interface, + name: final_name, + short_description: final_short_description, + listen_port: listen_port != null ? listen_port : undefined, + }; + + if (is_main_host) { + insert_data.id = 0; + } + const insert_host = await BunSQLite.insert< BUN_SQLITE_WGUI_HOSTS, TableType >({ table: "hosts", - data: [ - { - user_id: user.id, - wg_ip_address: wg_ip, - public_ip_address: final_public_ip || undefined, - interface: final_interface, - }, - ], + data: [insert_data], + update_on_duplicate: true, }); - if (!insert_host.success) { + if (!insert_host.success || !insert_host.postInsertReturn?.insertId) { return { success: false, msg: `Could not create the host record: ${insert_host.msg}`, @@ -70,13 +91,13 @@ export default async function createWireguardHost({ wg_ip_address: wg_ip, public_ip_address: final_public_ip || undefined, interface: final_interface, + name: final_name, + short_description: final_short_description, + listen_port: listen_port != null ? listen_port : undefined, }; if (host_id === 0 && final_interface) { - await BunSQLite.insert< - BUN_SQLITE_WGUI_VARIABLES, - TableType - >({ + await BunSQLite.insert({ table: "variables", data: [ { diff --git a/src/functions/backend/setup/get-next-available-listen-port.ts b/src/functions/backend/setup/get-next-available-listen-port.ts new file mode 100644 index 0000000..e9f45f0 --- /dev/null +++ b/src/functions/backend/setup/get-next-available-listen-port.ts @@ -0,0 +1,40 @@ +import { exec } from "node:child_process"; +import { promisify } from "node:util"; +import { AppData } from "@/src/data/app-data"; + +const execAsync = promisify(exec); + +/** + * Function to get the next available listen port on the system. + * Checks both UDP and TCP listening ports. + */ +export default async function getNextAvailableListenPort(): Promise { + const defaultPort = AppData["DefaultWGListenPort"] ?? 51820; + + try { + const { stdout } = await execAsync("ss -tuln"); + const usedPorts = new Set(); + + for (const line of stdout.split("\n")) { + const match = line.match(/[:](\d+)\s/); + if (match && match[1]) { + const port = Number.parseInt(match[1], 10); + if (!Number.isNaN(port)) { + usedPorts.add(port); + } + } + } + + let port = defaultPort; + while (usedPorts.has(port)) { + port++; + if (port > 65535) { + return defaultPort; + } + } + + return port; + } catch (error) { + return defaultPort; + } +} diff --git a/src/functions/backend/setup/setup-wireguard-client.ts b/src/functions/backend/setup/setup-wireguard-client.ts index 019c509..e49130c 100644 --- a/src/functions/backend/setup/setup-wireguard-client.ts +++ b/src/functions/backend/setup/setup-wireguard-client.ts @@ -14,13 +14,8 @@ import BunSQLite from "@moduletrace/bun-sqlite"; import type { TableType, User } from "@/src/types"; import grabClientDirnames from "./grab-client-dir-names"; -const { - WGUI_LIB_HOSTS_CONFIGS_DIR, - WIREGUARD_PRIVATE_KEY_FILE_NAME, - WIREGUARD_PUBLIC_KEY_FILE_NAME, - WGUI_LIB_HOST_CLIENTS_DIR_NAME, - WIREGUARD_CLIENT_CONFIG_FILE_NAME, -} = grabDirNames(); +const { WIREGUARD_PUBLIC_KEY_FILE_NAME, WIREGUARD_CLIENT_CONFIG_FILE_NAME } = + grabDirNames(); type Params = { client: BUN_SQLITE_WGUI_CLIENTS; @@ -135,7 +130,7 @@ export default async function setupWireguardClient({ sh += `\n`; sh += `[Peer]\n`; sh += `PublicKey = ${HOST_PUBLIC_KEY}\n`; - sh += `Endpoint = ${PUBLIC_IP_ADDRESS}:51820\n`; + sh += `Endpoint = ${PUBLIC_IP_ADDRESS}:${host?.listen_port || AppData["DefaultWGListenPort"]}\n`; if (client?.allow_all_ips == 1) { sh += `AllowedIPs = 0.0.0.0/0, ::/0\n`; diff --git a/src/functions/backend/setup/setup-wireguard-host.ts b/src/functions/backend/setup/setup-wireguard-host.ts index 7cb9f2d..87b837c 100644 --- a/src/functions/backend/setup/setup-wireguard-host.ts +++ b/src/functions/backend/setup/setup-wireguard-host.ts @@ -15,6 +15,7 @@ import manageWireguardHost from "./manage-wireguard-host"; import grabHostDirnames from "./grab-host-dir-names"; import grabClientDirnames from "./grab-client-dir-names"; import buildHostIptablesScripts from "./build-host-iptables-scripts"; +import { AppData } from "@/src/data/app-data"; const { WIREGUARD_PRIVATE_KEY_FILE_NAME, WIREGUARD_PUBLIC_KEY_FILE_NAME } = grabDirNames(); @@ -100,7 +101,8 @@ export default async function setupWireguardHost({ rules_by_client_id.set(rule.client_id, existing_rules); } - const TARGET_INTERFACE = host?.interface || await grabHostNetworkInterface(); + const TARGET_INTERFACE = + host?.interface || (await grabHostNetworkInterface()); const HOST_WG_IP = host?.wg_ip_address || variables?.find((v) => v.key == "main_host_wg_ip_address")?.value || @@ -246,7 +248,7 @@ export default async function setupWireguardHost({ sh += `cat > ${INTERFACE_NAME}.conf << EOF\n`; sh += `[Interface]\n`; sh += `Address = ${HOST_WG_IP}/24\n`; - sh += `ListenPort = 51820\n`; + sh += `ListenPort = ${host?.listen_port || AppData["DefaultWGListenPort"]}\n`; sh += `PrivateKey = ${HOST_PRIVATE_KEY}\n`; sh += `PostUp = ${POST_UP_PATH}\n`; sh += `PostDown = ${POST_DOWN_PATH}\n`; @@ -284,6 +286,13 @@ export default async function setupWireguardHost({ const exec = execSync(sh, { encoding: "utf-8" }); + if (!is_update_after_client_setup) { + execSync( + `ip link set ${INTERFACE_NAME} down 2>/dev/null; ip link del ${INTERFACE_NAME} 2>/dev/null; true`, + { encoding: "utf-8" }, + ); + } + const manage_res = manageWireguardHost({ host_id: HOST_ID, action: "restart", diff --git a/src/pages/admin/hosts/[host_id]/edit/(sections)/edit-host-form-section.tsx b/src/pages/admin/hosts/[host_id]/edit/(sections)/edit-host-form-section.tsx index 1a13601..69ca94c 100644 --- a/src/pages/admin/hosts/[host_id]/edit/(sections)/edit-host-form-section.tsx +++ b/src/pages/admin/hosts/[host_id]/edit/(sections)/edit-host-form-section.tsx @@ -86,13 +86,13 @@ export default function EditHostFormSection() {

- + /> */} {status?.error && status.msg ? ( ); -} \ No newline at end of file +} diff --git a/src/pages/admin/hosts/add/(functions)/submit-add-host-form.ts b/src/pages/admin/hosts/add/(functions)/submit-add-host-form.ts index cab4441..b88d514 100644 --- a/src/pages/admin/hosts/add/(functions)/submit-add-host-form.ts +++ b/src/pages/admin/hosts/add/(functions)/submit-add-host-form.ts @@ -24,6 +24,9 @@ export default async function submitAddHostForm({ wg_ip_address: form.wg_ip_address || "", public_ip_address: form.public_ip_address || "", interface: form.interface || "", + name: form.name || "", + short_description: form.short_description || "", + listen_port: form.listen_port != null ? Number(form.listen_port) : null, }, }, ); diff --git a/src/pages/admin/hosts/add/(partials)/add-host-form/add-host-form-description.tsx b/src/pages/admin/hosts/add/(partials)/add-host-form/add-host-form-description.tsx new file mode 100644 index 0000000..821469f --- /dev/null +++ b/src/pages/admin/hosts/add/(partials)/add-host-form/add-host-form-description.tsx @@ -0,0 +1,26 @@ +import Stack from "@/src/components/twui/layout/Stack"; +import useFormInit from "@/src/hooks/use-form-init"; +import type { BUN_SQLITE_WGUI_HOSTS } from "@/db/types/db"; +import Textarea from "@/src/components/twui/form/Textarea"; + +type Props = ReturnType>; + +export default function AddHostFormDescription({ form, setForm }: Props) { + return ( + +