Add public IP field and Network interface to host form
This commit is contained in:
@@ -222,6 +222,10 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
|
||||
fieldName: "public_ip_address",
|
||||
dataType: "TEXT",
|
||||
},
|
||||
{
|
||||
fieldName: "interface",
|
||||
dataType: "TEXT",
|
||||
},
|
||||
{
|
||||
fieldName: "wg_ip_address",
|
||||
dataType: "TEXT",
|
||||
|
||||
+2
-1
@@ -132,6 +132,7 @@ export type BUN_SQLITE_WGUI_HOSTS = {
|
||||
updated_at?: number | "";
|
||||
user_id?: number | "";
|
||||
public_ip_address?: string;
|
||||
interface?: string;
|
||||
wg_ip_address?: string;
|
||||
public_key?: string;
|
||||
}
|
||||
@@ -171,7 +172,7 @@ export type BUN_SQLITE_WGUI_VARIABLES = {
|
||||
* The time when the record was updated. (Unix Timestamp)
|
||||
*/
|
||||
updated_at?: number | "";
|
||||
key?: "main_host_wg_ip_address" | "main_host_public_ip_address" | "main_host_wg_public_key" | "main_host_wg_private_key" | "";
|
||||
key?: "main_host_wg_ip_address" | "main_host_public_ip_address" | "main_host_wg_public_key" | "main_host_wg_private_key" | "main_host_public_interface" | "";
|
||||
value?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,8 @@ export default function HostWgIpField({
|
||||
WireGuard IP Address
|
||||
</Span>
|
||||
}
|
||||
title="Wireguard IP Address"
|
||||
showLabel
|
||||
componentRef={inputRef}
|
||||
autoFocus={autoFocus}
|
||||
/>
|
||||
|
||||
@@ -19,4 +19,9 @@ export const Variables = [
|
||||
value: "main_host_wg_private_key",
|
||||
description: `Private Key for the main host`,
|
||||
},
|
||||
{
|
||||
title: `Main Host Public Interface`,
|
||||
value: "main_host_public_interface",
|
||||
description: `Network interface for the main host (e.g. eth0, wlan0)`,
|
||||
},
|
||||
] as const;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BUN_SQLITE_WGUI_HOSTS } 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";
|
||||
@@ -7,6 +7,8 @@ import grabHostPublicIPAddress from "./grab-host-public-ip-address";
|
||||
|
||||
type Params = {
|
||||
wg_ip_address?: string | null;
|
||||
public_ip_address?: string | null;
|
||||
interface?: string | null;
|
||||
user: User;
|
||||
};
|
||||
|
||||
@@ -20,6 +22,8 @@ type Params = {
|
||||
*/
|
||||
export default async function createWireguardHost({
|
||||
wg_ip_address,
|
||||
public_ip_address,
|
||||
interface: iface,
|
||||
user,
|
||||
}: Params): Promise<APIResponseObject> {
|
||||
const wg_ip = (wg_ip_address || "").trim();
|
||||
@@ -31,7 +35,9 @@ export default async function createWireguardHost({
|
||||
};
|
||||
}
|
||||
|
||||
const public_ip_address = await grabHostPublicIPAddress();
|
||||
const detected_public_ip = await grabHostPublicIPAddress();
|
||||
const final_public_ip = public_ip_address?.trim() || detected_public_ip;
|
||||
const final_interface = iface?.trim() || undefined;
|
||||
|
||||
try {
|
||||
const insert_host = await BunSQLite.insert<
|
||||
@@ -43,7 +49,8 @@ export default async function createWireguardHost({
|
||||
{
|
||||
user_id: user.id,
|
||||
wg_ip_address: wg_ip,
|
||||
public_ip_address: public_ip_address || undefined,
|
||||
public_ip_address: final_public_ip || undefined,
|
||||
interface: final_interface,
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -61,9 +68,26 @@ export default async function createWireguardHost({
|
||||
id: host_id,
|
||||
user_id: user.id,
|
||||
wg_ip_address: wg_ip,
|
||||
public_ip_address: public_ip_address || undefined,
|
||||
public_ip_address: final_public_ip || undefined,
|
||||
interface: final_interface,
|
||||
};
|
||||
|
||||
if (host_id === 0 && final_interface) {
|
||||
await BunSQLite.insert<
|
||||
BUN_SQLITE_WGUI_VARIABLES,
|
||||
TableType
|
||||
>({
|
||||
table: "variables",
|
||||
data: [
|
||||
{
|
||||
key: "main_host_public_interface",
|
||||
value: final_interface,
|
||||
},
|
||||
],
|
||||
update_on_duplicate: true,
|
||||
});
|
||||
}
|
||||
|
||||
const setup_res = await setupWireguardHost({
|
||||
host,
|
||||
wg_subnet_ip: wg_ip,
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import os from "node:os";
|
||||
|
||||
const VIRTUAL_INTERFACE_PATTERNS = [
|
||||
/^wg\d+$/,
|
||||
/^docker\d*$/,
|
||||
/^br\d*$/,
|
||||
/^veth.*/,
|
||||
/^tun.*/,
|
||||
/^tap.*/,
|
||||
/^vlan.*/,
|
||||
/^bond\d*$/,
|
||||
/^macvlan.*/,
|
||||
/^ipvlan.*/,
|
||||
/^wgui\d+$/,
|
||||
/^virbr\d*$/,
|
||||
/^vboxnet\d*$/,
|
||||
/^vmnet\d*$/,
|
||||
];
|
||||
|
||||
function isVirtualInterface(name: string): boolean {
|
||||
return VIRTUAL_INTERFACE_PATTERNS.some((pattern) =>
|
||||
pattern.test(name),
|
||||
);
|
||||
}
|
||||
|
||||
function isPhysicalInterface(name: string): boolean {
|
||||
try {
|
||||
const devicePath = `/sys/class/net/${name}/device`;
|
||||
const result = execSync(`test -e ${devicePath} && echo yes || echo no`, {
|
||||
encoding: "utf-8",
|
||||
}).trim();
|
||||
return result === "yes";
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to grab all available physical network interfaces on the machine
|
||||
* @returns Array of interface names (e.g. ["enp2s0", "wlan0"])
|
||||
*/
|
||||
export default async function grabHostNetworkInterfaces(): Promise<string[]> {
|
||||
try {
|
||||
const interfaces = os.networkInterfaces();
|
||||
const names: string[] = [];
|
||||
|
||||
for (const [name, addrs] of Object.entries(interfaces)) {
|
||||
if (!addrs) continue;
|
||||
if (name === "lo") continue;
|
||||
if (isVirtualInterface(name)) continue;
|
||||
for (const addr of addrs) {
|
||||
if (addr.family === "IPv4" && !addr.internal) {
|
||||
if (isPhysicalInterface(name)) {
|
||||
names.push(name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (names.length > 0) {
|
||||
return names.sort();
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
try {
|
||||
const route = execSync(`ip -4 addr show`, { encoding: "utf-8" })
|
||||
.trim()
|
||||
.split(/\n/);
|
||||
const names: string[] = [];
|
||||
for (const line of route) {
|
||||
const match = line.match(/^\d+:\s+(\S+):/);
|
||||
if (match && match[1] && match[1] !== "lo") {
|
||||
const iface = match[1];
|
||||
if (!isVirtualInterface(iface) && isPhysicalInterface(iface)) {
|
||||
names.push(iface);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (names.length > 0) return names.sort();
|
||||
} catch (error) {}
|
||||
|
||||
return [`eth0`];
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export default async function setupWireguardHost({
|
||||
rules_by_client_id.set(rule.client_id, existing_rules);
|
||||
}
|
||||
|
||||
const TARGET_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 ||
|
||||
@@ -158,21 +158,29 @@ export default async function setupWireguardHost({
|
||||
}).trim();
|
||||
|
||||
if (HOST_ID == 0 && !is_update_after_client_setup) {
|
||||
const variables_data: Array<{ key: string; value: string }> = [
|
||||
{
|
||||
key: "main_host_wg_ip_address",
|
||||
value: HOST_WG_IP,
|
||||
},
|
||||
{
|
||||
key: "main_host_wg_public_key",
|
||||
value: HOST_PUBLIC_KEY,
|
||||
},
|
||||
];
|
||||
if (host?.interface) {
|
||||
variables_data.push({
|
||||
key: "main_host_public_interface",
|
||||
value: host.interface,
|
||||
});
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
],
|
||||
data: variables_data as BUN_SQLITE_WGUI_VARIABLES[],
|
||||
update_on_duplicate: true,
|
||||
});
|
||||
|
||||
|
||||
@@ -43,6 +43,10 @@ export default async function syncWireguardHosts() {
|
||||
(v) => v.key == "main_host_public_ip_address",
|
||||
)?.value;
|
||||
|
||||
const main_host_interface = variables.payload?.find(
|
||||
(v) => v.key == "main_host_public_interface",
|
||||
)?.value;
|
||||
|
||||
if (!main_host_ip) {
|
||||
throw new Error(`Main Host not set yet`);
|
||||
}
|
||||
@@ -52,6 +56,7 @@ export default async function syncWireguardHosts() {
|
||||
id: 0,
|
||||
public_ip_address: main_host_public_ip,
|
||||
wg_ip_address: main_host_ip,
|
||||
interface: main_host_interface,
|
||||
},
|
||||
...(hosts.payload || []),
|
||||
];
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import type { BUN_SQLITE_WGUI_ALL_TYPEDEFS } from "@/db/types/db";
|
||||
import useStatus from "@/src/components/twui/hooks/useStatus";
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useState,
|
||||
type Dispatch,
|
||||
type SetStateAction,
|
||||
} from "react";
|
||||
import { AppContext } from "../pages/__root";
|
||||
import type { ImageInputToBase64FunctionReturn } from "../components/twui/utils/form/imageInputToBase64";
|
||||
import EJSON from "../utils/ejson";
|
||||
@@ -32,6 +39,14 @@ type Params<T extends {} = BUN_SQLITE_WGUI_ALL_TYPEDEFS> = {
|
||||
* Is this the first user?
|
||||
*/
|
||||
is_first_user?: boolean;
|
||||
/**
|
||||
* Function to run before the `setReady`
|
||||
* dispatch is fired
|
||||
*/
|
||||
before_ready_function?: (params: {
|
||||
form: T;
|
||||
setForm: Dispatch<SetStateAction<T>>;
|
||||
}) => Promise<void>;
|
||||
};
|
||||
|
||||
export default function useFormInit<
|
||||
@@ -94,7 +109,22 @@ export default function useFormInit<
|
||||
}
|
||||
} catch (error) {
|
||||
} finally {
|
||||
setReady(true);
|
||||
if (params?.before_ready_function) {
|
||||
params
|
||||
.before_ready_function({ form, setForm })
|
||||
.then(() => {})
|
||||
.catch((e2) => {
|
||||
console.log(
|
||||
`Before ready function error:`,
|
||||
e2.message,
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setReady(true);
|
||||
});
|
||||
} else {
|
||||
setReady(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
@@ -22,6 +22,8 @@ export default async function submitAddHostForm({
|
||||
method: "POST",
|
||||
body: {
|
||||
wg_ip_address: form.wg_ip_address || "",
|
||||
public_ip_address: form.public_ip_address || "",
|
||||
interface: form.interface || "",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import type { BUN_SQLITE_WGUI_HOSTS } from "@/db/types/db";
|
||||
import Select from "@/src/components/twui/form/Select";
|
||||
import Stack from "@/src/components/twui/layout/Stack";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
import { useEffect, useState } from "react";
|
||||
import fetchApi from "@/src/components/twui/utils/fetch/fetchApi";
|
||||
import type { ApiReqParams } from "@/src/types";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
|
||||
type Props = ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_HOSTS>>;
|
||||
|
||||
export default function AddHostFormInterface({ form, setForm }: Props) {
|
||||
const [interfaces, setInterfaces] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchApi<ApiReqParams, APIResponseObject>(
|
||||
"/api/admin/network-interfaces",
|
||||
{ method: "GET" },
|
||||
).then((res) => {
|
||||
if (res.success && res.stringRes) {
|
||||
try {
|
||||
const parsed = JSON.parse(res.stringRes) as string[];
|
||||
setInterfaces(parsed);
|
||||
} catch {}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack className="w-full gap-2 items-stretch">
|
||||
<Select
|
||||
name="interface"
|
||||
label="Network Interface"
|
||||
showLabel
|
||||
options={interfaces.map((iface) => ({
|
||||
value: iface,
|
||||
title: iface,
|
||||
default: iface === form.interface,
|
||||
}))}
|
||||
changeHandler={(v) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
interface: v,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { BUN_SQLITE_WGUI_HOSTS } from "@/db/types/db";
|
||||
import Input from "@/src/components/twui/form/Input";
|
||||
import Stack from "@/src/components/twui/layout/Stack";
|
||||
import useFormInit from "@/src/hooks/use-form-init";
|
||||
import { useEffect } from "react";
|
||||
import fetchApi from "@/src/components/twui/utils/fetch/fetchApi";
|
||||
import type { ApiReqParams } from "@/src/types";
|
||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||
|
||||
type Props = ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_HOSTS>>;
|
||||
|
||||
export default function AddHostFormPublicIP({ form, setForm }: Props) {
|
||||
useEffect(() => {
|
||||
fetchApi<ApiReqParams, APIResponseObject>(
|
||||
"/api/admin/public-ip",
|
||||
{ method: "GET" },
|
||||
).then((res) => {
|
||||
if (res.success && res.stringRes) {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
public_ip_address: res.stringRes!,
|
||||
}));
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack className="w-full gap-2">
|
||||
<Input
|
||||
name="public_ip_address"
|
||||
label="Public IP Address"
|
||||
showLabel
|
||||
placeholder="e.g. 203.0.113.10"
|
||||
value={form.public_ip_address || ""}
|
||||
changeHandler={(v) => {
|
||||
setForm((prev) => ({
|
||||
...prev,
|
||||
public_ip_address: v,
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,8 @@ import submitAddHostForm from "../../(functions)/submit-add-host-form";
|
||||
import AddHostFormAction from "./add-host-form-action";
|
||||
import AddHostFormSubnet from "./add-host-form-subnet";
|
||||
import AddHostFormWgIpAddress from "./add-host-form-wg-ip-address";
|
||||
import AddHostFormPublicIP from "./add-host-form-public-ip";
|
||||
import AddHostFormInterface from "./add-host-form-interface";
|
||||
import LoadingRectangleBlock from "@/src/components/twui/layout/LoadingRectangleBlock";
|
||||
import type { HostWgIpFieldStatus } from "@/src/components/general/host-wg-ip-field";
|
||||
|
||||
@@ -35,8 +37,10 @@ export default function AddHostForm() {
|
||||
{status?.error && <Tag color="error">{status.msg}</Tag>}
|
||||
{loading && <LoadingOverlay />}
|
||||
{init.ready ? (
|
||||
<Stack className="w-full gap-2">
|
||||
<AddHostFormSubnet />
|
||||
<Stack className="w-full gap-6 items-stretch">
|
||||
{/* <AddHostFormSubnet /> */}
|
||||
<AddHostFormPublicIP {...init} />
|
||||
<AddHostFormInterface {...init} />
|
||||
<AddHostFormWgIpAddress
|
||||
{...init}
|
||||
setIpStatus={setIpStatus}
|
||||
|
||||
@@ -41,6 +41,8 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
||||
|
||||
return await createWireguardHost({
|
||||
wg_ip_address: body?.wg_ip_address,
|
||||
public_ip_address: body?.public_ip_address,
|
||||
interface: body?.interface,
|
||||
user,
|
||||
});
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import grabHostNetworkInterfaces from "@/src/functions/backend/setup/grab-host-network-interfaces";
|
||||
import type { ApiReqParams } from "@/src/types";
|
||||
import type {
|
||||
APIResponseObject,
|
||||
BunextAPIRouteHandler,
|
||||
} from "@moduletrace/bunext/types";
|
||||
|
||||
export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
||||
params,
|
||||
) => {
|
||||
if (params.req.method !== "GET") {
|
||||
return { success: false };
|
||||
}
|
||||
try {
|
||||
const interfaces = await grabHostNetworkInterfaces();
|
||||
return { success: true, stringRes: JSON.stringify(interfaces) };
|
||||
} catch (error: any) {
|
||||
return { success: false, msg: error.message };
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import grabHostPublicIPAddress from "@/src/functions/backend/setup/grab-host-public-ip-address";
|
||||
import type { ApiReqParams } from "@/src/types";
|
||||
import type {
|
||||
APIResponseObject,
|
||||
BunextAPIRouteHandler,
|
||||
} from "@moduletrace/bunext/types";
|
||||
|
||||
export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
||||
params,
|
||||
) => {
|
||||
if (params.req.method !== "GET") {
|
||||
return { success: false };
|
||||
}
|
||||
try {
|
||||
const public_ip = await grabHostPublicIPAddress();
|
||||
return { success: true, stringRes: public_ip };
|
||||
} catch (error: any) {
|
||||
return { success: false, msg: error.message };
|
||||
}
|
||||
};
|
||||
@@ -222,6 +222,7 @@ export type ApiReqParams<
|
||||
host_id?: string | number | null;
|
||||
client_id?: string | number | null;
|
||||
public_ip_address?: string | null;
|
||||
interface?: string | null;
|
||||
// client_rules?: BUN_SQLITE_WGUI_CLIENT_RULES[];
|
||||
|
||||
media_base_64?: string;
|
||||
|
||||
Reference in New Issue
Block a user