This commit is contained in:
2026-09-20 07:27:29 +01:00
parent dc2cd66b25
commit 8ec44ee0a7
70 changed files with 2657 additions and 128 deletions
+2
View File
@@ -111,6 +111,8 @@ export default async function loginUser({
},
]);
console.log("new_logged_in_user", new_logged_in_user);
console.log(
`User #${new_logged_in_user.id} [${new_logged_in_user.first_name} ${new_logged_in_user.last_name}] logged in successfully.`,
);
@@ -0,0 +1,31 @@
import type { BUN_SQLITE_WGUI_CLIENT_RULES } from "@/db/types/db";
import type { TableType } from "@/src/types";
import BunSQLite from "@moduletrace/bun-sqlite";
type Params = {
client_id?: string | number;
client_ids?: (string | number)[];
};
export default async function grabClientRules({
client_id,
client_ids,
}: Params) {
const ids = client_id
? [client_id]
: (client_ids || []).filter((id) => id !== undefined && id !== "");
if (!ids[0]) {
return [] as BUN_SQLITE_WGUI_CLIENT_RULES[];
}
const res = await BunSQLite.select<BUN_SQLITE_WGUI_CLIENT_RULES, TableType>({
table: "client_rules",
});
const id_set = new Set(ids.map((id) => Number(id)));
return (res.payload || []).filter(
(rule) => rule.client_id && id_set.has(Number(rule.client_id)),
);
}
@@ -0,0 +1,134 @@
import { describe, expect, test } from "bun:test";
import buildHostIptablesScripts from "./build-host-iptables-scripts";
const base = {
host_id: 0,
interface_name: "wgui0",
target_interface: "eth0",
};
describe("buildHostIptablesScripts", () => {
test("deny by default with no clients", () => {
const res = buildHostIptablesScripts({
...base,
clients: [],
});
expect(res.success).toBe(true);
expect(res.post_up).toContain("iptables -N WGUI0FWD");
expect(res.post_up).toContain("iptables -A WGUI0FWD -j DROP");
expect(res.post_up).toContain("iptables -A WGUI0IN -j DROP");
expect(res.post_up).not.toContain("WGUI0FWD -s");
expect(res.post_down).toContain("iptables -X WGUI0FWD");
expect(res.post_down).toContain("iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE");
});
test("all access accepts the client on forward and input", () => {
const res = buildHostIptablesScripts({
...base,
clients: [
{
id: 1,
wg_ip_address: "10.0.0.2",
rules: [{ rule_type: "all" }],
},
],
});
expect(res.success).toBe(true);
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT",
);
expect(res.post_up).toContain(
"iptables -A WGUI0IN -s 10.0.0.2/32 -j ACCEPT",
);
});
test("destination IP and ports", () => {
const res = buildHostIptablesScripts({
...base,
clients: [
{
id: 2,
wg_ip_address: "10.0.0.3",
rules: [
{
rule_type: "destination",
destination: "192.168.1.10",
ports: "80,443",
protocol: "tcp",
},
],
},
],
});
expect(res.success).toBe(true);
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.3/32 -d 192.168.1.10/32 -p tcp -m multiport --dports 80,443 -j ACCEPT",
);
expect(res.post_up).toContain(
"iptables -A WGUI0IN -s 10.0.0.3/32 -d 192.168.1.10/32 -p tcp -m multiport --dports 80,443 -j ACCEPT",
);
});
test("protocol any with ports emits tcp and udp", () => {
const res = buildHostIptablesScripts({
...base,
clients: [
{
id: 3,
wg_ip_address: "10.0.0.4",
rules: [
{
rule_type: "destination",
ports: "53",
protocol: "any",
},
],
},
],
});
expect(res.success).toBe(true);
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.4/32 -p tcp --dport 53 -j ACCEPT",
);
expect(res.post_up).toContain(
"iptables -A WGUI0FWD -s 10.0.0.4/32 -p udp --dport 53 -j ACCEPT",
);
});
test("rejects invalid destination", () => {
const res = buildHostIptablesScripts({
...base,
clients: [
{
id: 4,
wg_ip_address: "10.0.0.5",
rules: [
{
rule_type: "destination",
destination: "not-an-ip",
},
],
},
],
});
expect(res.success).toBe(false);
});
test("cleans up legacy allow-all rules", () => {
const res = buildHostIptablesScripts({
...base,
});
expect(res.post_up).toContain(
"iptables -D FORWARD -i wgui0 -j ACCEPT 2>/dev/null || true",
);
expect(res.post_down).toContain(
"iptables -D FORWARD -i wgui0 -j ACCEPT 2>/dev/null || true",
);
});
});
@@ -0,0 +1,288 @@
import type { BUN_SQLITE_WGUI_CLIENT_RULES } from "@/db/types/db";
import parsePortList from "@/src/utils/parse-port-list";
import validateIpv4 from "@/src/utils/validate-ipv4";
import deriveIptablesChainNames from "./derive-iptables-chain-names";
import normalizeIptablesDestination from "./normalize-iptables-destination";
import validateClientRule from "./validate-client-rule";
type HostIptablesClient = {
id?: number | "";
wg_ip_address?: string;
rules?: BUN_SQLITE_WGUI_CLIENT_RULES[];
};
type Params = {
host_id: number;
interface_name: string;
target_interface: string;
clients?: HostIptablesClient[];
};
const SAFE_IFACE = /^[A-Za-z][A-Za-z0-9._-]*$/;
const MULTIPORT_LIMIT = 15;
function joinArgs(parts: (string | undefined)[]) {
return parts.filter(Boolean).join(" ");
}
function protocolsForRule({
protocol,
has_ports,
}: {
protocol?: string;
has_ports: boolean;
}) {
if (has_ports) {
if (protocol == "udp") {
return ["udp"];
}
if (protocol == "tcp") {
return ["tcp"];
}
return ["tcp", "udp"];
}
if (protocol == "tcp" || protocol == "udp") {
return [protocol];
}
return [undefined];
}
function portMatch({ ports }: { ports: string[] }) {
if (!ports[0]) {
return;
}
if (ports.length == 1) {
return `--dport ${ports[0]}`;
}
return `-m multiport --dports ${ports.join(",")}`;
}
function chunkPorts({ ports }: { ports: string[] }) {
if (ports.length <= MULTIPORT_LIMIT) {
return [ports];
}
const chunks: string[][] = [];
for (let i = 0; i < ports.length; i += MULTIPORT_LIMIT) {
chunks.push(ports.slice(i, i + MULTIPORT_LIMIT));
}
return chunks;
}
function acceptLines({
chain,
source,
destination,
protocol,
ports,
}: {
chain: string;
source: string;
destination?: string;
protocol?: string;
ports: string[];
}) {
const lines: string[] = [];
const protocols = protocolsForRule({
protocol,
has_ports: Boolean(ports[0]),
});
const port_chunks = ports[0] ? chunkPorts({ ports }) : [[]];
for (let p = 0; p < protocols.length; p++) {
const proto = protocols[p];
for (let c = 0; c < port_chunks.length; c++) {
const chunk = port_chunks[c] || [];
lines.push(
joinArgs([
`iptables -A ${chain}`,
`-s ${source}`,
destination ? `-d ${destination}` : undefined,
proto ? `-p ${proto}` : undefined,
proto ? portMatch({ ports: chunk }) : undefined,
`-j ACCEPT`,
]),
);
}
}
return lines;
}
export default function buildHostIptablesScripts({
host_id,
interface_name,
target_interface,
clients,
}: Params) {
if (!Number.isInteger(host_id) || host_id < 0) {
return {
success: false,
msg: `Invalid host id`,
};
}
if (!SAFE_IFACE.test(interface_name)) {
return {
success: false,
msg: `Invalid interface name`,
};
}
if (!SAFE_IFACE.test(target_interface)) {
return {
success: false,
msg: `Invalid target interface`,
};
}
const { forward, input } = deriveIptablesChainNames({ host_id });
const accept_lines: string[] = [];
const host_clients = clients || [];
for (let i = 0; i < host_clients.length; i++) {
const client = host_clients[i];
if (!client) {
continue;
}
const source_ip = client.wg_ip_address?.trim();
if (!source_ip) {
return {
success: false,
msg: `Client ${client.id || i} is missing a WireGuard IP`,
};
}
if (!validateIpv4({ ip: source_ip })) {
return {
success: false,
msg: `Client ${client.id || i} has an invalid WireGuard IP`,
};
}
const source = `${source_ip}/32`;
const rules = client.rules || [];
for (let r = 0; r < rules.length; r++) {
const rule = rules[r];
if (!rule) {
continue;
}
const valid = validateClientRule({ rule });
if (!valid.success) {
return {
success: false,
msg: `Client ${client.id || i}: ${valid.msg}`,
};
}
if (rule.rule_type == "all") {
accept_lines.push(
`iptables -A ${forward} -s ${source} -j ACCEPT`,
);
accept_lines.push(
`iptables -A ${input} -s ${source} -j ACCEPT`,
);
continue;
}
const destination = normalizeIptablesDestination({
destination: rule.destination,
});
const parsed_ports = parsePortList({ ports: rule.ports });
if (!parsed_ports.success) {
return {
success: false,
msg: `Client ${client.id || i}: ${parsed_ports.msg}`,
};
}
accept_lines.push(
...acceptLines({
chain: forward,
source,
destination,
protocol: rule.protocol || "any",
ports: parsed_ports.ports,
}),
...acceptLines({
chain: input,
source,
destination,
protocol: rule.protocol || "any",
ports: parsed_ports.ports,
}),
);
}
}
const post_up = [
`#!/bin/bash`,
``,
`iptables -D INPUT -i ${interface_name} -j ACCEPT 2>/dev/null || true`,
`iptables -D OUTPUT -o ${interface_name} -j ACCEPT 2>/dev/null || true`,
`iptables -D FORWARD -i ${interface_name} -j ACCEPT 2>/dev/null || true`,
`iptables -D FORWARD -o ${interface_name} -j ACCEPT 2>/dev/null || true`,
``,
`iptables -N ${forward} 2>/dev/null || true`,
`iptables -F ${forward}`,
`iptables -N ${input} 2>/dev/null || true`,
`iptables -F ${input}`,
``,
`iptables -A ${forward} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT`,
`iptables -A ${input} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT`,
``,
...accept_lines,
``,
`iptables -A ${forward} -j DROP`,
`iptables -A ${input} -j DROP`,
``,
`iptables -C FORWARD -i ${interface_name} -j ${forward} 2>/dev/null || iptables -I FORWARD 1 -i ${interface_name} -j ${forward}`,
`iptables -C FORWARD -o ${interface_name} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || iptables -I FORWARD 1 -o ${interface_name} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT`,
`iptables -C INPUT -i ${interface_name} -j ${input} 2>/dev/null || iptables -I INPUT 1 -i ${interface_name} -j ${input}`,
``,
`iptables -t nat -C POSTROUTING -o ${target_interface} -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -o ${target_interface} -j MASQUERADE`,
``,
].join("\n");
const post_down = [
`#!/bin/bash`,
``,
`iptables -D FORWARD -i ${interface_name} -j ${forward} 2>/dev/null || true`,
`iptables -D FORWARD -o ${interface_name} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null || true`,
`iptables -D INPUT -i ${interface_name} -j ${input} 2>/dev/null || true`,
`iptables -D INPUT -i ${interface_name} -j ACCEPT 2>/dev/null || true`,
`iptables -D OUTPUT -o ${interface_name} -j ACCEPT 2>/dev/null || true`,
`iptables -D FORWARD -i ${interface_name} -j ACCEPT 2>/dev/null || true`,
`iptables -D FORWARD -o ${interface_name} -j ACCEPT 2>/dev/null || true`,
`iptables -F ${forward} 2>/dev/null || true`,
`iptables -X ${forward} 2>/dev/null || true`,
`iptables -F ${input} 2>/dev/null || true`,
`iptables -X ${input} 2>/dev/null || true`,
`iptables -t nat -D POSTROUTING -o ${target_interface} -j MASQUERADE 2>/dev/null || true`,
``,
].join("\n");
return {
success: true,
post_up,
post_down,
};
}
@@ -0,0 +1,10 @@
type Params = {
host_id: number;
};
export default function deriveIptablesChainNames({ host_id }: Params) {
return {
forward: `WGUI${host_id}FWD`,
input: `WGUI${host_id}IN`,
};
}
@@ -0,0 +1,31 @@
import type { BUN_SQLITE_WGUI_CLIENT_RULES } from "@/db/types/db";
type Params = {
rule: BUN_SQLITE_WGUI_CLIENT_RULES;
};
export default function expandClientRuleDestinations({ rule }: Params) {
if (rule.rule_type == "all") {
return [rule];
}
const destinations = (rule.destination || "")
.split(",")
.map((value) => value.trim())
.filter(Boolean);
if (destinations.length <= 1) {
return [
{
...rule,
destination: destinations[0] || "",
},
];
}
return destinations.map((destination) => ({
...rule,
id: undefined,
destination,
}));
}
@@ -49,19 +49,6 @@ export default function grabClientDirnames({
WIREGUARD_CLIENT_CONFIG_FILE_NAME,
);
const CLIENT_PRIVATE_KEY = execSync(`cat ${CLIENT_PRIVATE_KEY_FILE}`, {
encoding: "utf-8",
}).trim();
const CLIENT_PUBLIC_KEY = execSync(`cat ${CLIENT_PUBLIC_KEY_FILE}`, {
encoding: "utf-8",
}).trim();
const HOST_PUBLIC_KEY = execSync(
`cat ${path.join(HOST_CONFIG_DIR, WIREGUARD_PUBLIC_KEY_FILE_NAME)}`,
{ encoding: "utf-8" },
).trim();
return {
CLIENT_WG_IP,
HOST_CLIENTS_DIR,
@@ -70,8 +57,5 @@ export default function grabClientDirnames({
CLIENT_PRIVATE_KEY_FILE,
CLIENT_PUBLIC_KEY_FILE,
CLIENT_CONFIG_FILE,
CLIENT_PRIVATE_KEY,
CLIENT_PUBLIC_KEY,
HOST_PUBLIC_KEY,
};
}
@@ -37,7 +37,9 @@ export default function manageWireguardHost({
const MANAGE_COMMAND = `${WGUI_WG_QUICK_MANAGE_SCRIPT} ${action} ${INTERFACE_NAME} ${HOST_CONFIG_FILE}`;
try {
const output = execSync(MANAGE_COMMAND, { encoding: "utf-8" }).trim();
const output = execSync(MANAGE_COMMAND, {
encoding: "utf-8",
}).trim();
return {
success: true,
@@ -0,0 +1,24 @@
import validateIpv4 from "@/src/utils/validate-ipv4";
import validateIpv4Cidr from "@/src/utils/validate-ipv4-cidr";
type Params = {
destination?: string;
};
export default function normalizeIptablesDestination({ destination }: Params) {
const value = destination?.trim();
if (!value) {
return;
}
if (!validateIpv4Cidr({ value })) {
return;
}
if (validateIpv4({ ip: value })) {
return `${value}/32`;
}
return value;
}
@@ -0,0 +1,35 @@
import type { BUN_SQLITE_WGUI_CLIENT_RULES } from "@/db/types/db";
type Params = {
rule?: BUN_SQLITE_WGUI_CLIENT_RULES | null;
client_id?: number | "";
user_id?: number | "";
};
export default function sanitizeClientRule({
rule,
client_id,
user_id,
}: Params): BUN_SQLITE_WGUI_CLIENT_RULES | undefined {
if (!rule) {
return;
}
const rule_type = rule.rule_type == "all" ? "all" : "destination";
return {
...(rule.id ? { id: rule.id } : {}),
user_id: user_id || rule.user_id,
client_id: client_id || rule.client_id,
rule_type,
destination:
rule_type == "all" ? "" : (rule.destination || "").trim(),
ports: rule_type == "all" ? "" : (rule.ports || "").trim(),
protocol:
rule_type == "all"
? "any"
: rule.protocol == "tcp" || rule.protocol == "udp"
? rule.protocol
: "any",
};
}
@@ -64,11 +64,7 @@ export default async function setupWireguardClient({
CLIENT_DIR,
CLIENT_PRIVATE_KEY_FILE,
CLIENT_PUBLIC_KEY_FILE,
HOST_CLIENTS_DIR,
HOST_CONFIG_DIR,
CLIENT_PRIVATE_KEY,
CLIENT_PUBLIC_KEY,
HOST_PUBLIC_KEY,
} = grabClientDirnames({ client, host_id });
if (!CLIENT_WG_IP) {
@@ -114,6 +110,19 @@ export default async function setupWireguardClient({
client?.public_ip_address ||
HOST_WG_IP;
const CLIENT_PRIVATE_KEY = execSync(`cat ${CLIENT_PRIVATE_KEY_FILE}`, {
encoding: "utf-8",
}).trim();
const CLIENT_PUBLIC_KEY = execSync(`cat ${CLIENT_PUBLIC_KEY_FILE}`, {
encoding: "utf-8",
}).trim();
const HOST_PUBLIC_KEY = execSync(
`cat ${path.join(HOST_CONFIG_DIR, WIREGUARD_PUBLIC_KEY_FILE_NAME)}`,
{ encoding: "utf-8" },
).trim();
let sh = ``;
sh += `cd ${CLIENT_DIR}\n`;
@@ -1,5 +1,6 @@
import type {
BUN_SQLITE_WGUI_CLIENTS,
BUN_SQLITE_WGUI_CLIENT_RULES,
BUN_SQLITE_WGUI_HOSTS,
BUN_SQLITE_WGUI_VARIABLES,
} from "@/db/types/db";
@@ -12,8 +13,8 @@ import type { TableType, User } from "@/src/types";
import checkPrivateIPAvailability from "./check-private-ip-availability";
import manageWireguardHost from "./manage-wireguard-host";
import grabHostDirnames from "./grab-host-dir-names";
import path from "node:path";
import grabClientDirnames from "./grab-client-dir-names";
import buildHostIptablesScripts from "./build-host-iptables-scripts";
const { WIREGUARD_PRIVATE_KEY_FILE_NAME, WIREGUARD_PUBLIC_KEY_FILE_NAME } =
grabDirNames();
@@ -69,6 +70,36 @@ export default async function setupWireguardHost({
const clients = host_clients_res.payload || [];
const client_rules_res = await BunSQLite.select<
BUN_SQLITE_WGUI_CLIENT_RULES,
TableType
>({
table: "client_rules",
});
const client_ids = new Set(
clients
.map((client) => client.id)
.filter((id): id is number => Boolean(id)),
);
const rules_by_client_id = new Map<
number,
BUN_SQLITE_WGUI_CLIENT_RULES[]
>();
for (let i = 0; i < (client_rules_res.payload || []).length; i++) {
const rule = client_rules_res.payload?.[i];
if (!rule?.client_id || !client_ids.has(rule.client_id)) {
continue;
}
const existing_rules = rules_by_client_id.get(rule.client_id) || [];
existing_rules.push(rule);
rules_by_client_id.set(rule.client_id, existing_rules);
}
const TARGET_INTERFACE = await grabHostNetworkInterface();
const HOST_WG_IP =
host?.wg_ip_address ||
@@ -172,37 +203,37 @@ export default async function setupWireguardHost({
sh += `cd ${HOST_CONFIG_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 += `chmod +x ${POST_UP_PATH}\n`;
const iptables_scripts = buildHostIptablesScripts({
host_id: Number(HOST_ID),
interface_name: INTERFACE_NAME,
target_interface: TARGET_INTERFACE || "eth0",
clients: clients.map((client) => ({
id: client.id,
wg_ip_address: client.wg_ip_address,
rules:
client.id && typeof client.id == "number"
? rules_by_client_id.get(client.id) || []
: [],
})),
});
sh += `\n`;
if (
!iptables_scripts.success ||
!iptables_scripts.post_up ||
!iptables_scripts.post_down
) {
return {
success: false,
msg: iptables_scripts.msg || `Could not build iptables scripts`,
};
}
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 += `chmod +x ${POST_DOWN_PATH}\n`;
sh += `\n`;
execSync(`mkdir -p ${HOST_IPTABLES_DIR}`, { encoding: "utf-8" });
await Bun.write(POST_UP_PATH, iptables_scripts.post_up);
await Bun.write(POST_DOWN_PATH, iptables_scripts.post_down);
execSync(`chmod +x ${POST_UP_PATH} ${POST_DOWN_PATH}`, {
encoding: "utf-8",
});
sh += `cat > ${INTERFACE_NAME}.conf << EOF\n`;
sh += `[Interface]\n`;
@@ -218,14 +249,21 @@ export default async function setupWireguardHost({
const client = clients[i];
if (!client?.id) continue;
const { CLIENT_PUBLIC_KEY } = grabClientDirnames({
const { CLIENT_PUBLIC_KEY_FILE } = grabClientDirnames({
client,
host_id: HOST_ID,
});
const CLIENT_PUBLIC_KEY = execSync(
`cat ${CLIENT_PUBLIC_KEY_FILE}`,
{
encoding: "utf-8",
},
).trim();
sh += `[Peer]\n`;
sh += `PublicKey = ${CLIENT_PUBLIC_KEY}\n`;
sh += `AllowedIPs = ${client.allowed_ips}\n`;
sh += `AllowedIPs = ${client.wg_ip_address}/32\n`;
sh += `\n`;
}
}
@@ -35,27 +35,40 @@ export default async function syncWireguardHosts() {
table: "hosts",
});
const main_host_id = AppData["WireguardHostID"];
const main_host_ip = variables.payload?.find(
(v) => v.key == "main_host_wg_ip_address",
)?.value;
const main_host_public_ip = variables.payload?.find(
(v) => v.key == "main_host_public_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 all_hosts: BUN_SQLITE_WGUI_HOSTS[] = [
{
id: 0,
public_ip_address: main_host_public_ip,
wg_ip_address: main_host_ip,
},
...(hosts.payload || []),
];
const res = manageWireguardHost({ action: "up", host_id });
for (const host of all_hosts) {
if (typeof host.id == "number") {
const res = manageWireguardHost({
action: "up",
host_id: host.id,
});
bunext.bunextLog.info(
`[wgui] wireguard host ${host_config_file_name}: ${
res.success ? `up` : `failed — ${res.msg}`
}`,
);
bunext.bunextLog.info(
`[wgui] wireguard host ${host.id}: ${
res.success ? `up` : `failed — ${res.msg}`
}`,
);
}
}
} catch (error: any) {
console.log(`[wgui] skipping wireguard host sync — ${error.message}`);
@@ -139,8 +139,6 @@ export default async function updateWireguardHostPublicIP({
skip_host_setup: true,
});
console.log("client_setup_res", client_setup_res);
if (client_setup_res.success) {
updated_count++;
} else {
@@ -0,0 +1,78 @@
import type { BUN_SQLITE_WGUI_CLIENT_RULES } from "@/db/types/db";
import {
ClientRuleProtocols,
ClientRuleTypes,
} from "@/src/dict/client-rules-dict";
import type { ClientRuleProtocol, ClientRuleType } from "@/src/types";
import parsePortList from "@/src/utils/parse-port-list";
import validateIpv4Cidr from "@/src/utils/validate-ipv4-cidr";
type Params = {
rule?: BUN_SQLITE_WGUI_CLIENT_RULES | null;
};
const RULE_TYPES = ClientRuleTypes.map((rt) => rt.value);
const PROTOCOLS = ClientRuleProtocols.map((p) => p.value);
export default function validateClientRule({ rule }: Params) {
if (!rule) {
return {
success: false,
msg: `No client rule provided`,
};
}
const rule_type = rule.rule_type as ClientRuleType | undefined;
if (!rule_type || !RULE_TYPES.includes(rule_type)) {
return {
success: false,
msg: `Invalid rule type`,
};
}
const protocol = (rule.protocol || "any") as ClientRuleProtocol;
if (!PROTOCOLS.includes(protocol)) {
return {
success: false,
msg: `Invalid protocol`,
};
}
if (rule_type == "all") {
return {
success: true,
};
}
const destination = rule.destination?.trim() || "";
const ports = rule.ports?.trim() || "";
if (!destination && !ports && protocol == "any") {
return {
success: false,
msg: `Destination rules need an IP, ports, or a protocol`,
};
}
if (destination && !validateIpv4Cidr({ value: destination })) {
return {
success: false,
msg: `Invalid destination "${destination}"`,
};
}
const parsed_ports = parsePortList({ ports });
if (!parsed_ports.success) {
return {
success: false,
msg: parsed_ports.msg,
};
}
return {
success: true,
};
}