feat: custom Stream port manager UI and WireGuard config Zip download API
This commit is contained in:
parent
7bf175da41
commit
34020bc562
4 changed files with 130 additions and 7 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import express from "express";
|
import express from "express";
|
||||||
|
import archiver from "archiver";
|
||||||
import internalWireguard from "../internal/wireguard.js";
|
import internalWireguard from "../internal/wireguard.js";
|
||||||
import internalAuditLog from "../internal/audit-log.js";
|
import internalAuditLog from "../internal/audit-log.js";
|
||||||
import jwtdecode from "../lib/express/jwt-decode.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;
|
export default router;
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,7 @@ export async function getWgClientConfig(id: number): Promise<string> {
|
||||||
export function downloadWgConfig(id: number, name: string) {
|
export function downloadWgConfig(id: number, name: string) {
|
||||||
return api.download({ url: `/wireguard/client/${id}/configuration` }, `${name}.conf`);
|
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`);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,11 @@ import {
|
||||||
IconServer,
|
IconServer,
|
||||||
IconEdit,
|
IconEdit,
|
||||||
IconLink,
|
IconLink,
|
||||||
|
IconZip,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import EasyModal from "ez-modal-react";
|
import EasyModal from "ez-modal-react";
|
||||||
import { useState } from "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 { Loading } from "src/components";
|
||||||
import {
|
import {
|
||||||
useWgClients,
|
useWgClients,
|
||||||
|
|
@ -154,6 +155,11 @@ function WireGuard() {
|
||||||
downloadWgConfig(id, cleanName);
|
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 (
|
return (
|
||||||
<div className="container-xl">
|
<div className="container-xl">
|
||||||
{/* Page Header */}
|
{/* Page Header */}
|
||||||
|
|
@ -411,6 +417,16 @@ function WireGuard() {
|
||||||
>
|
>
|
||||||
<IconDownload size={16} />
|
<IconDownload size={16} />
|
||||||
</button>
|
</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
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={`btn ${client.enabled ? "btn-outline-warning" : "btn-outline-success"}`}
|
className={`btn ${client.enabled ? "btn-outline-warning" : "btn-outline-success"}`}
|
||||||
|
|
|
||||||
82
install.sh
82
install.sh
|
|
@ -139,6 +139,16 @@ generate_docker_compose() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
log_step "Generating docker-compose.yml..."
|
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
|
cat > "$COMPOSE_FILE" <<YAML
|
||||||
services:
|
services:
|
||||||
d3v-npmwg:
|
d3v-npmwg:
|
||||||
|
|
@ -156,7 +166,7 @@ services:
|
||||||
- "81:81" # Admin UI
|
- "81:81" # Admin UI
|
||||||
- "443:443" # HTTPS
|
- "443:443" # HTTPS
|
||||||
- "51820-51830:51820-51830/udp" # WireGuard Multi-Server Range
|
- "51820-51830:51820-51830/udp" # WireGuard Multi-Server Range
|
||||||
volumes:
|
$(echo -e "$custom_ports_block" | sed '/^$/d') volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
- ./letsencrypt:/etc/letsencrypt
|
- ./letsencrypt:/etc/letsencrypt
|
||||||
- ./wireguard:/etc/wireguard
|
- ./wireguard:/etc/wireguard
|
||||||
|
|
@ -395,6 +405,62 @@ do_update() {
|
||||||
log_ok "Done."
|
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)
|
# 7. Toggle Port 81 (Admin UI)
|
||||||
# -----------------------------------------------------------
|
# -----------------------------------------------------------
|
||||||
|
|
@ -435,10 +501,11 @@ show_menu() {
|
||||||
echo " 3) Uninstall D3V-NPMWG + Docker (Purge)"
|
echo " 3) Uninstall D3V-NPMWG + Docker (Purge)"
|
||||||
echo " 4) Reset Admin Password"
|
echo " 4) Reset Admin Password"
|
||||||
echo " 5) Update D3V-NPMWG"
|
echo " 5) Update D3V-NPMWG"
|
||||||
echo " 6) Toggle Admin Port 81 (Block/Unblock)"
|
echo " 6) Manage Custom Stream Ports"
|
||||||
echo " 7) Exit"
|
echo " 7) Toggle Admin Port 81 (Block/Unblock)"
|
||||||
|
echo " 8) Exit"
|
||||||
separator
|
separator
|
||||||
read -rp " Select [1-7]: " choice
|
read -rp " Select [1-8]: " choice
|
||||||
echo ""
|
echo ""
|
||||||
case "$choice" in
|
case "$choice" in
|
||||||
1) do_install ;;
|
1) do_install ;;
|
||||||
|
|
@ -446,8 +513,9 @@ show_menu() {
|
||||||
3) do_purge ;;
|
3) do_purge ;;
|
||||||
4) do_reset_password ;;
|
4) do_reset_password ;;
|
||||||
5) do_update ;;
|
5) do_update ;;
|
||||||
6) do_toggle_port_81 ;;
|
6) do_manage_ports ;;
|
||||||
7) echo "Bye!"; exit 0 ;;
|
7) do_toggle_port_81 ;;
|
||||||
|
8) echo "Bye!"; exit 0 ;;
|
||||||
*) log_err "Invalid option." ;;
|
*) log_err "Invalid option." ;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
@ -462,6 +530,7 @@ show_help() {
|
||||||
echo " purge Remove D3V-NPMWG AND Docker"
|
echo " purge Remove D3V-NPMWG AND Docker"
|
||||||
echo " reset Reset web admin password"
|
echo " reset Reset web admin password"
|
||||||
echo " update Pull latest image and restart"
|
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 " toggle-port Block or unblock external access to Admin UI (Port 81) using iptables"
|
||||||
echo " help Show this help"
|
echo " help Show this help"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
@ -480,6 +549,7 @@ else
|
||||||
purge) do_purge ;;
|
purge) do_purge ;;
|
||||||
reset) do_reset_password ;;
|
reset) do_reset_password ;;
|
||||||
update) do_update ;;
|
update) do_update ;;
|
||||||
|
manage-ports) do_manage_ports ;;
|
||||||
toggle-port) do_toggle_port_81 ;;
|
toggle-port) do_toggle_port_81 ;;
|
||||||
help|-h|--help) show_help ;;
|
help|-h|--help) show_help ;;
|
||||||
*)
|
*)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue