feat: custom Stream port manager UI and WireGuard config Zip download API

This commit is contained in:
xtcnet 2026-03-08 15:50:25 +07:00
parent 7bf175da41
commit 34020bc562
4 changed files with 130 additions and 7 deletions

View file

@ -1,4 +1,5 @@
import express from "express";
import archiver from "archiver";
import internalWireguard from "../internal/wireguard.js";
import internalAuditLog from "../internal/audit-log.js";
import jwtdecode from "../lib/express/jwt-decode.js";
@ -274,4 +275,36 @@ router.get("/client/:id/qrcode.svg", async (req, res, next) => {
}
});
/**
* GET /api/wireguard/client/:id/configuration.zip
* Download WireGuard client configuration as a ZIP archive
*/
router.get("/client/:id/configuration.zip", async (req, res, next) => {
try {
const knex = db();
const client = await knex("wg_client").where("id", req.params.id).first();
if (!client) {
return res.status(404).json({ error: { message: "Client not found" } });
}
const configStr = await internalWireguard.getClientConfiguration(knex, req.params.id);
const svgStr = await internalWireguard.getClientQRCode(knex, req.params.id);
const safeName = client.name.replace(/[^a-zA-Z0-9_.-]/g, "-").substring(0, 32);
res.set("Content-Disposition", `attachment; filename="${safeName}.zip"`);
res.set("Content-Type", "application/zip");
const archive = archiver("zip", { zlib: { level: 9 } });
archive.on("error", (err) => next(err));
archive.pipe(res);
archive.append(configStr, { name: `${safeName}.conf` });
archive.append(svgStr, { name: `${safeName}-qrcode.svg` });
await archive.finalize();
} catch (err) {
next(err);
}
});
export default router;

View file

@ -79,3 +79,7 @@ export async function getWgClientConfig(id: number): Promise<string> {
export function downloadWgConfig(id: number, name: string) {
return api.download({ url: `/wireguard/client/${id}/configuration` }, `${name}.conf`);
}
export function downloadWgConfigZip(id: number, name: string) {
return api.download({ url: `/wireguard/client/${id}/configuration.zip` }, `${name}.zip`);
}

View file

@ -9,10 +9,11 @@ import {
IconServer,
IconEdit,
IconLink,
IconZip,
} from "@tabler/icons-react";
import EasyModal from "ez-modal-react";
import { useState } from "react";
import { downloadWgConfig } from "src/api/backend/wireguard";
import { downloadWgConfig, downloadWgConfigZip } from "src/api/backend/wireguard";
import { Loading } from "src/components";
import {
useWgClients,
@ -154,6 +155,11 @@ function WireGuard() {
downloadWgConfig(id, cleanName);
};
const handleDownloadZip = (id: number, name: string) => {
const cleanName = name.replace(/[^a-zA-Z0-9_.-]/g, "-").substring(0, 32);
downloadWgConfigZip(id, cleanName);
};
return (
<div className="container-xl">
{/* Page Header */}
@ -411,6 +417,16 @@ function WireGuard() {
>
<IconDownload size={16} />
</button>
<button
type="button"
className="btn btn-outline-primary"
title="Download Config + QR (ZIP)"
onClick={() =>
handleDownloadZip(client.id, client.name)
}
>
<IconZip size={16} />
</button>
<button
type="button"
className={`btn ${client.enabled ? "btn-outline-warning" : "btn-outline-success"}`}

View file

@ -139,6 +139,16 @@ generate_docker_compose() {
fi
log_step "Generating docker-compose.yml..."
local custom_ports_block=""
if [ -f ".custom_ports" ]; then
while IFS= read -r port_mapping; do
# Ignore empty lines or comments
[[ -z "$port_mapping" || "$port_mapping" =~ ^# ]] && continue
custom_ports_block+=" - \"${port_mapping}\"\n"
done < ".custom_ports"
fi
cat > "$COMPOSE_FILE" <<YAML
services:
d3v-npmwg:
@ -156,7 +166,7 @@ services:
- "81:81" # Admin UI
- "443:443" # HTTPS
- "51820-51830:51820-51830/udp" # WireGuard Multi-Server Range
volumes:
$(echo -e "$custom_ports_block" | sed '/^$/d') volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
- ./wireguard:/etc/wireguard
@ -395,6 +405,62 @@ do_update() {
log_ok "Done."
}
# -----------------------------------------------------------
# 7. Toggle Port 81 (Admin UI)
# -----------------------------------------------------------
# x. Custom Stream Ports Manager
# -----------------------------------------------------------
do_manage_ports() {
require_root
echo ""
log_step "TCP/UDP Stream Ports Manager"
echo "If you created a Stream in Nginx Proxy Manager (e.g., listening on port 10000),"
echo "you must expose that port down to the Docker container."
echo ""
local custom_ports_file=".custom_ports"
touch "$custom_ports_file"
echo "Current custom exposed ports:"
if [ -s "$custom_ports_file" ]; then
cat -n "$custom_ports_file"
else
echo " (None)"
fi
echo ""
read -rp "$(echo -e "${CYAN}[?]${NC} Enter new port mapping (e.g. 10000:10000) or 'clear' to remove all: ")" new_port
if [[ "$new_port" == "clear" ]]; then
> "$custom_ports_file"
log_ok "All custom ports cleared."
elif [[ -n "$new_port" ]]; then
echo "$new_port" >> "$custom_ports_file"
log_ok "Port $new_port added."
else
log_warn "No changes made."
return
fi
log_step "Regenerating docker-compose.yml and restarting container..."
local dc
dc=$(get_compose_cmd)
local current_wg_host=""
if [ -f "docker-compose.yml" ]; then
current_wg_host=$(grep -E 'WG_HOST:' docker-compose.yml | awk -F'"' '{print $2}')
fi
if [ -z "$current_wg_host" ]; then
current_wg_host=$(detect_public_ip)
fi
generate_docker_compose "$current_wg_host"
$dc up -d
log_ok "Container updated with new port configurations."
}
# -----------------------------------------------------------
# 7. Toggle Port 81 (Admin UI)
# -----------------------------------------------------------
@ -435,10 +501,11 @@ show_menu() {
echo " 3) Uninstall D3V-NPMWG + Docker (Purge)"
echo " 4) Reset Admin Password"
echo " 5) Update D3V-NPMWG"
echo " 6) Toggle Admin Port 81 (Block/Unblock)"
echo " 7) Exit"
echo " 6) Manage Custom Stream Ports"
echo " 7) Toggle Admin Port 81 (Block/Unblock)"
echo " 8) Exit"
separator
read -rp " Select [1-7]: " choice
read -rp " Select [1-8]: " choice
echo ""
case "$choice" in
1) do_install ;;
@ -446,8 +513,9 @@ show_menu() {
3) do_purge ;;
4) do_reset_password ;;
5) do_update ;;
6) do_toggle_port_81 ;;
7) echo "Bye!"; exit 0 ;;
6) do_manage_ports ;;
7) do_toggle_port_81 ;;
8) echo "Bye!"; exit 0 ;;
*) log_err "Invalid option." ;;
esac
done
@ -462,6 +530,7 @@ show_help() {
echo " purge Remove D3V-NPMWG AND Docker"
echo " reset Reset web admin password"
echo " update Pull latest image and restart"
echo " manage-ports Add or remove custom exposed Stream TCP/UDP ports"
echo " toggle-port Block or unblock external access to Admin UI (Port 81) using iptables"
echo " help Show this help"
echo ""
@ -480,6 +549,7 @@ else
purge) do_purge ;;
reset) do_reset_password ;;
update) do_update ;;
manage-ports) do_manage_ports ;;
toggle-port) do_toggle_port_81 ;;
help|-h|--help) show_help ;;
*)