Apply client changes to the running tunnel without a restart
Saving a client or rule restarted the whole host tunnel (wg-quick down/up) and re-ran the firewall manager setup, which was slow and briefly dropped every client on the host. Client and rule changes now sync peers with `wg syncconf` and re-run the PostUp script, skipping the firewalld/ufw step. Host creation and edits still do a full restart. PostUp now loads the per-host chains with `iptables-restore --noflush`, so re-running it on a live tunnel swaps the rules atomically instead of leaving the chains empty between flush and re-add. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import grabHostDirnames from "./grab-host-dir-names";
|
||||||
|
import manageWireguardHost from "./manage-wireguard-host";
|
||||||
|
|
||||||
|
type Params = {
|
||||||
|
host_id: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function errorOutput({ error }: { error: any }) {
|
||||||
|
const stderr = error?.stderr ? String(error.stderr).trim() : "";
|
||||||
|
const stdout = error?.stdout ? String(error.stdout).trim() : "";
|
||||||
|
|
||||||
|
return stderr || stdout || error?.message || "unknown error";
|
||||||
|
}
|
||||||
|
|
||||||
|
function isInterfaceUp({ interface_name }: { interface_name: string }) {
|
||||||
|
try {
|
||||||
|
execFileSync("ip", ["link", "show", "dev", interface_name], {
|
||||||
|
stdio: "pipe",
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies an already-written host config and PostUp script to the running
|
||||||
|
* tunnel without restarting it: peers are synced with `wg syncconf` and the
|
||||||
|
* per-host iptables chains are reloaded atomically by re-running PostUp.
|
||||||
|
* Connected clients stay connected. Falls back to bringing the tunnel up if
|
||||||
|
* it isn't running.
|
||||||
|
*
|
||||||
|
* Only for changes that leave the interface itself alone (peers and client
|
||||||
|
* rules). Address or listen-port changes need a full restart.
|
||||||
|
*/
|
||||||
|
export default function applyWireguardHostLive({
|
||||||
|
host_id,
|
||||||
|
}: Params): APIResponseObject {
|
||||||
|
const { HOST_CONFIG_FILE, INTERFACE_NAME, POST_UP_PATH } =
|
||||||
|
grabHostDirnames({ host: { id: host_id } });
|
||||||
|
|
||||||
|
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",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isInterfaceUp({ interface_name: INTERFACE_NAME })) {
|
||||||
|
return manageWireguardHost({ host_id, action: "up" });
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const stripped = execFileSync("wg-quick", ["strip", HOST_CONFIG_FILE], {
|
||||||
|
encoding: "utf-8",
|
||||||
|
stdio: "pipe",
|
||||||
|
});
|
||||||
|
|
||||||
|
execFileSync("wg", ["syncconf", INTERFACE_NAME, "/dev/stdin"], {
|
||||||
|
input: stripped,
|
||||||
|
stdio: "pipe",
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
msg: `Could not sync peers on ${INTERFACE_NAME}: ${errorOutput({ error })}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
execFileSync(POST_UP_PATH, [], {
|
||||||
|
encoding: "utf-8",
|
||||||
|
stdio: "pipe",
|
||||||
|
timeout: 30000,
|
||||||
|
});
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
msg: `Peers synced on ${INTERFACE_NAME}, but the firewall rules could not be applied: ${errorOutput({ error })}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
msg: `Applied changes to ${INTERFACE_NAME} without a restart`,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -14,9 +14,9 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain("iptables -N WGUI0FWD");
|
expect(res.post_up).toContain(":WGUI0FWD - [0:0]");
|
||||||
expect(res.post_up).toContain("iptables -A WGUI0FWD -j DROP");
|
expect(res.post_up).toContain("-A WGUI0FWD -j DROP");
|
||||||
expect(res.post_up).toContain("iptables -A WGUI0IN -j DROP");
|
expect(res.post_up).toContain("-A WGUI0IN -j DROP");
|
||||||
expect(res.post_up).not.toContain("WGUI0FWD -s");
|
expect(res.post_up).not.toContain("WGUI0FWD -s");
|
||||||
expect(res.post_down).toContain("iptables -X WGUI0FWD");
|
expect(res.post_down).toContain("iptables -X WGUI0FWD");
|
||||||
expect(res.post_down).toContain(
|
expect(res.post_down).toContain(
|
||||||
@@ -38,10 +38,10 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT",
|
"-A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0IN -s 10.0.0.2/32 -j ACCEPT",
|
"-A WGUI0IN -s 10.0.0.2/32 -j ACCEPT",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,10 +66,10 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain(
|
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",
|
"-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(
|
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",
|
"-A WGUI0IN -s 10.0.0.3/32 -d 192.168.1.10/32 -p tcp -m multiport --dports 80,443 -j ACCEPT",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,13 +95,13 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.5.0.2/32 -d 65.108.81.79/32 -p tcp --dport 443 -j ACCEPT",
|
"-A WGUI0FWD -s 10.5.0.2/32 -d 65.108.81.79/32 -p tcp --dport 443 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0IN -s 10.5.0.2/32 -d 65.108.81.79/32 -p tcp --dport 443 -j ACCEPT",
|
"-A WGUI0IN -s 10.5.0.2/32 -d 65.108.81.79/32 -p tcp --dport 443 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).not.toContain(
|
expect(res.post_up).not.toContain(
|
||||||
"iptables -A WGUI0IN -s 10.5.0.2/32 -d 10.5.0.0/24 -p tcp --dport 443 -j ACCEPT",
|
"-A WGUI0IN -s 10.5.0.2/32 -d 10.5.0.0/24 -p tcp --dport 443 -j ACCEPT",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -125,16 +125,16 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.0.0.4/32 -p tcp --dport 53 -j ACCEPT",
|
"-A WGUI0FWD -s 10.0.0.4/32 -p tcp --dport 53 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.0.0.4/32 -p udp --dport 53 -j ACCEPT",
|
"-A WGUI0FWD -s 10.0.0.4/32 -p udp --dport 53 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0IN -s 10.0.0.4/32 -p tcp --dport 53 -j ACCEPT",
|
"-A WGUI0IN -s 10.0.0.4/32 -p tcp --dport 53 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0IN -s 10.0.0.4/32 -p udp --dport 53 -j ACCEPT",
|
"-A WGUI0IN -s 10.0.0.4/32 -p udp --dport 53 -j ACCEPT",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -229,10 +229,10 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.0.0.7/32 -j ACCEPT",
|
"-A WGUI0FWD -s 10.0.0.7/32 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0IN -s 10.0.0.7/32 -d 10.6.0.0/24 -j ACCEPT",
|
"-A WGUI0IN -s 10.0.0.7/32 -d 10.6.0.0/24 -j ACCEPT",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -256,10 +256,10 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.0.0.8/32 -p tcp --dport 53 -j ACCEPT",
|
"-A WGUI0FWD -s 10.0.0.8/32 -p tcp --dport 53 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0IN -s 10.0.0.8/32 -p tcp --dport 53 -j ACCEPT",
|
"-A WGUI0IN -s 10.0.0.8/32 -p tcp --dport 53 -j ACCEPT",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -278,10 +278,10 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT",
|
"-A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0IN -s 10.0.0.2/32 -d 10.0.0.0/24 -j ACCEPT",
|
"-A WGUI0IN -s 10.0.0.2/32 -d 10.0.0.0/24 -j ACCEPT",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -307,10 +307,10 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
const post_up = res.post_up ?? "";
|
const post_up = res.post_up ?? "";
|
||||||
|
|
||||||
const sibling_drop = "iptables -A WGUI0FWD -s 10.0.0.2/32 -d 10.1.0.0/24 -j DROP";
|
const sibling_drop = "-A WGUI0FWD -s 10.0.0.2/32 -d 10.1.0.0/24 -j DROP";
|
||||||
const sibling_drop_2 = "iptables -A WGUI0FWD -s 10.0.0.2/32 -d 10.2.0.0/24 -j DROP";
|
const sibling_drop_2 = "-A WGUI0FWD -s 10.0.0.2/32 -d 10.2.0.0/24 -j DROP";
|
||||||
const sibling_input_drop = "iptables -A WGUI0IN -s 10.0.0.2/32 -d 10.1.0.0/24 -j DROP";
|
const sibling_input_drop = "-A WGUI0IN -s 10.0.0.2/32 -d 10.1.0.0/24 -j DROP";
|
||||||
const accept = "iptables -A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT";
|
const accept = "-A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT";
|
||||||
|
|
||||||
expect(post_up).toContain(sibling_drop);
|
expect(post_up).toContain(sibling_drop);
|
||||||
expect(post_up).toContain(sibling_drop_2);
|
expect(post_up).toContain(sibling_drop_2);
|
||||||
@@ -337,7 +337,7 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).not.toContain("-d 10.1.0.0/24 -j DROP");
|
expect(res.post_up).not.toContain("-d 10.1.0.0/24 -j DROP");
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT",
|
"-A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -356,10 +356,10 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.7.0.3/32 -j ACCEPT",
|
"-A WGUI0FWD -s 10.7.0.3/32 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0IN -s 10.7.0.3/32 -j ACCEPT",
|
"-A WGUI0IN -s 10.7.0.3/32 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).not.toContain("-d 10.7.0.0/24");
|
expect(res.post_up).not.toContain("-d 10.7.0.0/24");
|
||||||
});
|
});
|
||||||
@@ -382,8 +382,8 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
const post_up = res.post_up ?? "";
|
const post_up = res.post_up ?? "";
|
||||||
|
|
||||||
const sibling_drop =
|
const sibling_drop =
|
||||||
"iptables -A WGUI0IN -s 10.7.0.4/32 -d 10.3.0.0/24 -j DROP";
|
"-A WGUI0IN -s 10.7.0.4/32 -d 10.3.0.0/24 -j DROP";
|
||||||
const input_accept = "iptables -A WGUI0IN -s 10.7.0.4/32 -j ACCEPT";
|
const input_accept = "-A WGUI0IN -s 10.7.0.4/32 -j ACCEPT";
|
||||||
|
|
||||||
expect(post_up).toContain(sibling_drop);
|
expect(post_up).toContain(sibling_drop);
|
||||||
expect(post_up).toContain(input_accept);
|
expect(post_up).toContain(input_accept);
|
||||||
@@ -414,7 +414,7 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0FWD -s 10.7.0.5/32 -j ACCEPT",
|
"-A WGUI0FWD -s 10.7.0.5/32 -j ACCEPT",
|
||||||
);
|
);
|
||||||
expect(res.post_up).not.toContain("192.0.2.1");
|
expect(res.post_up).not.toContain("192.0.2.1");
|
||||||
});
|
});
|
||||||
@@ -444,9 +444,9 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
const post_up = res.post_up ?? "";
|
const post_up = res.post_up ?? "";
|
||||||
|
|
||||||
const sibling_drop =
|
const sibling_drop =
|
||||||
"iptables -A WGUI0IN -s 10.0.0.6/32 -d 10.1.0.0/24 -j DROP";
|
"-A WGUI0IN -s 10.0.0.6/32 -d 10.1.0.0/24 -j DROP";
|
||||||
const accept =
|
const accept =
|
||||||
"iptables -A WGUI0IN -s 10.0.0.6/32 -d 10.1.0.25/32 -p tcp --dport 80 -j ACCEPT";
|
"-A WGUI0IN -s 10.0.0.6/32 -d 10.1.0.25/32 -p tcp --dport 80 -j ACCEPT";
|
||||||
|
|
||||||
expect(post_up).toContain(sibling_drop);
|
expect(post_up).toContain(sibling_drop);
|
||||||
expect(post_up).toContain(accept);
|
expect(post_up).toContain(accept);
|
||||||
@@ -464,7 +464,7 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
|
|
||||||
expect(res.success).toBe(true);
|
expect(res.success).toBe(true);
|
||||||
expect(res.post_up).toContain(
|
expect(res.post_up).toContain(
|
||||||
"iptables -A WGUI0IN -i wgui0 -p tcp --dport 8443 -j ACCEPT",
|
"-A WGUI0IN -i wgui0 -p tcp --dport 8443 -j ACCEPT",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -489,4 +489,27 @@ describe("buildHostIptablesScripts", () => {
|
|||||||
expect(res.success).toBe(false);
|
expect(res.success).toBe(false);
|
||||||
expect(res.msg).toBe("Invalid host id");
|
expect(res.msg).toBe("Invalid host id");
|
||||||
});
|
});
|
||||||
|
test("loads the host chains atomically with iptables-restore", () => {
|
||||||
|
const res = buildHostIptablesScripts({
|
||||||
|
...base,
|
||||||
|
clients: [
|
||||||
|
{ id: 1, wg_ip_address: "10.0.0.2", allow_all_ips: true },
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const post_up = res.post_up ?? "";
|
||||||
|
const start = post_up.indexOf(
|
||||||
|
"iptables-restore --noflush <<'WGUI_RULES' || exit 1",
|
||||||
|
);
|
||||||
|
const end = post_up.indexOf("\nWGUI_RULES\n");
|
||||||
|
const block = post_up.slice(start, end);
|
||||||
|
|
||||||
|
expect(start).toBeGreaterThan(-1);
|
||||||
|
expect(end).toBeGreaterThan(start);
|
||||||
|
expect(block).toContain("*filter\n:WGUI0FWD - [0:0]\n:WGUI0IN - [0:0]");
|
||||||
|
expect(block).toContain("-A WGUI0FWD -s 10.0.0.2/32 -j ACCEPT");
|
||||||
|
expect(block.trimEnd().endsWith("-A WGUI0IN -j DROP\nCOMMIT")).toBe(true);
|
||||||
|
expect(post_up).not.toContain("iptables -F WGUI0FWD");
|
||||||
|
expect(post_up.indexOf("iptables -I FORWARD 1 -i wgui0 -j WGUI0FWD")).toBeGreaterThan(end);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ function acceptLines({
|
|||||||
|
|
||||||
lines.push(
|
lines.push(
|
||||||
joinArgs([
|
joinArgs([
|
||||||
`iptables -A ${forward_chain}`,
|
`-A ${forward_chain}`,
|
||||||
`-s ${source}`,
|
`-s ${source}`,
|
||||||
direction,
|
direction,
|
||||||
proto ? `-p ${proto}` : undefined,
|
proto ? `-p ${proto}` : undefined,
|
||||||
@@ -130,7 +130,7 @@ function acceptLines({
|
|||||||
|
|
||||||
lines.push(
|
lines.push(
|
||||||
joinArgs([
|
joinArgs([
|
||||||
`iptables -A ${input_chain}`,
|
`-A ${input_chain}`,
|
||||||
`-s ${source}`,
|
`-s ${source}`,
|
||||||
input_destination ? `-d ${input_destination}` : undefined,
|
input_destination ? `-d ${input_destination}` : undefined,
|
||||||
proto ? `-p ${proto}` : undefined,
|
proto ? `-p ${proto}` : undefined,
|
||||||
@@ -198,7 +198,7 @@ export default function buildHostIptablesScripts({
|
|||||||
}
|
}
|
||||||
|
|
||||||
accept_lines.push(
|
accept_lines.push(
|
||||||
`iptables -A ${input} -i ${interface_name} -p tcp --dport ${port} -j ACCEPT`,
|
`-A ${input} -i ${interface_name} -p tcp --dport ${port} -j ACCEPT`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,15 +231,15 @@ export default function buildHostIptablesScripts({
|
|||||||
for (let s = 0; s < sibling_host_subnets.length; s++) {
|
for (let s = 0; s < sibling_host_subnets.length; s++) {
|
||||||
const sibling_host_subnet = sibling_host_subnets[s];
|
const sibling_host_subnet = sibling_host_subnets[s];
|
||||||
accept_lines.push(
|
accept_lines.push(
|
||||||
`iptables -A ${forward} -s ${source} -d ${sibling_host_subnet} -j DROP`,
|
`-A ${forward} -s ${source} -d ${sibling_host_subnet} -j DROP`,
|
||||||
`iptables -A ${input} -s ${source} -d ${sibling_host_subnet} -j DROP`,
|
`-A ${input} -s ${source} -d ${sibling_host_subnet} -j DROP`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client.allow_all_ips) {
|
if (client.allow_all_ips) {
|
||||||
accept_lines.push(
|
accept_lines.push(
|
||||||
`iptables -A ${forward} -s ${source} -j ACCEPT`,
|
`-A ${forward} -s ${source} -j ACCEPT`,
|
||||||
`iptables -A ${input} -s ${source} -j ACCEPT`,
|
`-A ${input} -s ${source} -j ACCEPT`,
|
||||||
);
|
);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -264,12 +264,12 @@ export default function buildHostIptablesScripts({
|
|||||||
if (rule.rule_type == "all") {
|
if (rule.rule_type == "all") {
|
||||||
accept_lines.push(
|
accept_lines.push(
|
||||||
joinArgs([
|
joinArgs([
|
||||||
`iptables -A ${forward}`,
|
`-A ${forward}`,
|
||||||
`-s ${source}`,
|
`-s ${source}`,
|
||||||
`-j ACCEPT`,
|
`-j ACCEPT`,
|
||||||
]),
|
]),
|
||||||
joinArgs([
|
joinArgs([
|
||||||
`iptables -A ${input}`,
|
`-A ${input}`,
|
||||||
`-s ${source}`,
|
`-s ${source}`,
|
||||||
scoped_subnet ? `-d ${scoped_subnet}` : undefined,
|
scoped_subnet ? `-d ${scoped_subnet}` : undefined,
|
||||||
`-j ACCEPT`,
|
`-j ACCEPT`,
|
||||||
@@ -314,21 +314,22 @@ export default function buildHostIptablesScripts({
|
|||||||
`iptables -D FORWARD -i ${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 -D FORWARD -o ${interface_name} -j ACCEPT 2>/dev/null || true`,
|
||||||
``,
|
``,
|
||||||
`iptables -N ${forward} 2>/dev/null || true`,
|
// Declaring a chain under --noflush creates or flushes it, and the
|
||||||
`iptables -F ${forward}`,
|
// whole table commits at once — so re-running this on a live tunnel
|
||||||
`iptables -N ${input} 2>/dev/null || true`,
|
// swaps the rules without a window where the chains are empty.
|
||||||
`iptables -F ${input}`,
|
`iptables-restore --noflush <<'WGUI_RULES' || exit 1`,
|
||||||
``,
|
`*filter`,
|
||||||
`iptables -A ${forward} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT`,
|
`:${forward} - [0:0]`,
|
||||||
`iptables -A ${input} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT`,
|
`:${input} - [0:0]`,
|
||||||
``,
|
`-A ${forward} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT`,
|
||||||
`iptables -A ${input} -i ${interface_name} -p udp --dport 53 -j ACCEPT`,
|
`-A ${input} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT`,
|
||||||
`iptables -A ${input} -i ${interface_name} -p tcp --dport 53 -j ACCEPT`,
|
`-A ${input} -i ${interface_name} -p udp --dport 53 -j ACCEPT`,
|
||||||
``,
|
`-A ${input} -i ${interface_name} -p tcp --dport 53 -j ACCEPT`,
|
||||||
...accept_lines,
|
...accept_lines,
|
||||||
``,
|
`-A ${forward} -j DROP`,
|
||||||
`iptables -A ${forward} -j DROP`,
|
`-A ${input} -j DROP`,
|
||||||
`iptables -A ${input} -j DROP`,
|
`COMMIT`,
|
||||||
|
`WGUI_RULES`,
|
||||||
``,
|
``,
|
||||||
`iptables -C FORWARD -i ${interface_name} -j ${forward} 2>/dev/null || iptables -I FORWARD 1 -i ${interface_name} -j ${forward}`,
|
`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 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`,
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ export default async function setupWireguardClient({
|
|||||||
host: resolved_host,
|
host: resolved_host,
|
||||||
user,
|
user,
|
||||||
is_update_after_client_setup: true,
|
is_update_after_client_setup: true,
|
||||||
|
apply_live: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!host_setup_res.success) {
|
if (!host_setup_res.success) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import BunSQLite from "@moduletrace/bun-sqlite";
|
|||||||
import type { TableType, User } from "@/src/types";
|
import type { TableType, User } from "@/src/types";
|
||||||
import checkPrivateIPAvailability from "./check-private-ip-availability";
|
import checkPrivateIPAvailability from "./check-private-ip-availability";
|
||||||
import manageWireguardHost from "./manage-wireguard-host";
|
import manageWireguardHost from "./manage-wireguard-host";
|
||||||
|
import applyWireguardHostLive from "./apply-wireguard-host-live";
|
||||||
import grabHostDirnames from "./grab-host-dir-names";
|
import grabHostDirnames from "./grab-host-dir-names";
|
||||||
import grabClientDirnames from "./grab-client-dir-names";
|
import grabClientDirnames from "./grab-client-dir-names";
|
||||||
import buildHostIptablesScripts from "./build-host-iptables-scripts";
|
import buildHostIptablesScripts from "./build-host-iptables-scripts";
|
||||||
@@ -27,6 +28,13 @@ type Params = {
|
|||||||
wg_subnet_ip?: string;
|
wg_subnet_ip?: string;
|
||||||
user: User;
|
user: User;
|
||||||
is_update_after_client_setup?: boolean;
|
is_update_after_client_setup?: boolean;
|
||||||
|
/**
|
||||||
|
* Apply the new config to the running tunnel (`wg syncconf` + atomic
|
||||||
|
* iptables reload) instead of restarting it, and skip the firewall
|
||||||
|
* manager rules. Only for peer/rule changes — the interface address and
|
||||||
|
* listen port must be unchanged.
|
||||||
|
*/
|
||||||
|
apply_live?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function setupWireguardHost({
|
export default async function setupWireguardHost({
|
||||||
@@ -34,6 +42,7 @@ export default async function setupWireguardHost({
|
|||||||
wg_subnet_ip,
|
wg_subnet_ip,
|
||||||
user,
|
user,
|
||||||
is_update_after_client_setup,
|
is_update_after_client_setup,
|
||||||
|
apply_live,
|
||||||
}: Params): Promise<APIResponseObject> {
|
}: Params): Promise<APIResponseObject> {
|
||||||
const {
|
const {
|
||||||
HOST_CLIENTS_DIR,
|
HOST_CLIENTS_DIR,
|
||||||
@@ -296,6 +305,10 @@ export default async function setupWireguardHost({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (apply_live) {
|
||||||
|
return applyWireguardHostLive({ host_id: Number(HOST_ID) });
|
||||||
|
}
|
||||||
|
|
||||||
const manage_res = manageWireguardHost({
|
const manage_res = manageWireguardHost({
|
||||||
host_id: HOST_ID,
|
host_id: HOST_ID,
|
||||||
action: "restart",
|
action: "restart",
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ export default async function updateWireguardHostPublicIP({
|
|||||||
host,
|
host,
|
||||||
user,
|
user,
|
||||||
is_update_after_client_setup: true,
|
is_update_after_client_setup: true,
|
||||||
|
apply_live: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!host_setup_res.success) {
|
if (!host_setup_res.success) {
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ export default async function (
|
|||||||
host,
|
host,
|
||||||
user,
|
user,
|
||||||
is_update_after_client_setup: true,
|
is_update_after_client_setup: true,
|
||||||
|
apply_live: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!host_setup_res.success) {
|
if (!host_setup_res.success) {
|
||||||
|
|||||||
Reference in New Issue
Block a user