From e9983449f8ebed39deb530d2d565c89c2459af88 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Wed, 4 Feb 2026 09:43:17 -0300 Subject: [PATCH] suspend/unsuspend peer with interface reload --- wireguard_peer/views.py | 27 +++++++++------ wireguard_tools/functions.py | 67 ++++++++++++++++++++++++++++++++++++ wireguard_tools/views.py | 4 +-- 3 files changed, 86 insertions(+), 12 deletions(-) create mode 100644 wireguard_tools/functions.py diff --git a/wireguard_peer/views.py b/wireguard_peer/views.py index 1feef1d..93477d5 100644 --- a/wireguard_peer/views.py +++ b/wireguard_peer/views.py @@ -18,6 +18,8 @@ from wgwadmlibrary.tools import check_sort_order_conflict, deduplicate_sort_orde from wireguard.models import Peer, PeerAllowedIP, WireGuardInstance from wireguard_peer.forms import PeerAllowedIPForm, PeerNameForm, PeerKeepaliveForm, PeerKeysForm, PeerSuspensionForm, \ PeerScheduleProfileForm +from wireguard_tools.functions import func_reload_wireguard_interface +from wireguard_tools.views import export_wireguard_configuration def generate_peer_default(wireguard_instance): @@ -425,8 +427,6 @@ def view_wireguard_peer_suspend(request): if form.is_valid(): form.save() messages.success(request, _('Peer suspension/unsuspension scheduled successfully.')) - current_peer.wireguard_instance.pending_changes = True - current_peer.wireguard_instance.save() else: messages.error(request, _('Error scheduling peer suspension/unsuspension. Please correct the errors below.')) @@ -436,24 +436,31 @@ def view_wireguard_peer_suspend(request): peer_scheduling.manual_suspend_reason = None peer_scheduling.save() messages.success(request, _('Schedule cleared successfully.')) - current_peer.wireguard_instance.pending_changes = True - current_peer.wireguard_instance.save() elif action == 'suspend_now': current_peer.suspended = True current_peer.suspend_reason = manual_suspend_reason current_peer.save() - messages.success(request, _('Peer suspended successfully.')) - current_peer.wireguard_instance.pending_changes = True - current_peer.wireguard_instance.save() + + export_wireguard_configuration(current_peer.wireguard_instance) + success, message = func_reload_wireguard_interface(current_peer.wireguard_instance) + if success: + messages.success(request, _('Peer suspended successfully.')) + else: + messages.error(request, _('Peer suspended, but failed to reload WireGuard interface: ') + message) elif action == 'unsuspend_now': current_peer.suspended = False current_peer.suspend_reason = '' current_peer.save() - messages.success(request, _('Peer reactivated successfully.')) - current_peer.wireguard_instance.pending_changes = True - current_peer.wireguard_instance.save() + + export_wireguard_configuration(current_peer.wireguard_instance) + success, message = func_reload_wireguard_interface(current_peer.wireguard_instance) + if success: + messages.success(request, _('Peer reactivated successfully.')) + else: + messages.error(request, _('Peer reactivated, but failed to reload WireGuard interface: ') + message) + else: messages.error(request, _('Invalid action.')) diff --git a/wireguard_tools/functions.py b/wireguard_tools/functions.py new file mode 100644 index 0000000..cf81562 --- /dev/null +++ b/wireguard_tools/functions.py @@ -0,0 +1,67 @@ +import os +import subprocess +from typing import Union + +from wireguard.models import WireGuardInstance + + +def func_reload_wireguard_interface(target: Union[str, WireGuardInstance], config_dir: str = "/etc/wireguard") -> tuple[bool, str]: + """ + Reload a WireGuard interface safely using: + wg syncconf <(wg-quick strip ) + + Accepts: + - "wg0" + - "wg0.conf" + - WireGuardInstance object + + Returns: + (success: bool, message: str) + """ + + # Resolve interface name and config path + if isinstance(target, WireGuardInstance): + interface_name = f"wg{target.instance_id}" + conf_filename = f"{interface_name}.conf" + else: + name = target.replace(".conf", "") + interface_name = name + conf_filename = f"{name}.conf" + + conf_path = os.path.join(config_dir, conf_filename) + + if not os.path.exists(conf_path): + return False, f"Config file not found: {conf_path}" + + try: + # First, run wg-quick strip to produce a clean wg-compatible config + strip_proc = subprocess.run( + ["wg-quick", "strip", conf_path], + capture_output=True, + text=True, + check=True, + ) + + # Write stripped config to a temp file + temp_path = f"/tmp/wgstrip_{interface_name}.conf" + with open(temp_path, "w") as f: + f.write(strip_proc.stdout) + + # Apply syncconf + sync_proc = subprocess.run( + ["wg", "syncconf", interface_name, temp_path], + capture_output=True, + text=True, + ) + + os.remove(temp_path) + + if sync_proc.returncode != 0: + return False, sync_proc.stderr.strip() + + return True, f"{interface_name} reloaded successfully" + + except subprocess.CalledProcessError as e: + return False, e.stderr.strip() if e.stderr else str(e) + except Exception as e: + return False, str(e) diff --git a/wireguard_tools/views.py b/wireguard_tools/views.py index 5590111..4ca2545 100644 --- a/wireguard_tools/views.py +++ b/wireguard_tools/views.py @@ -102,7 +102,7 @@ def export_wireguard_configuration(instance_only: WireGuardInstance = None): instance_to_execute_firewall, force_export_all_instances = set_instance_to_include_firewall() if instance_only and not force_export_all_instances: - instances = WireGuardInstance.objects.filter(id=instance_only.id) + instances = WireGuardInstance.objects.filter(uuid=instance_only.uuid) cleanup_orphaned = False else: instances = WireGuardInstance.objects.all() @@ -230,8 +230,8 @@ def view_export_wireguard_configs(request): if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=30).exists(): return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) - export_firewall_configuration() export_wireguard_configuration() + export_firewall_configuration() if request.GET.get('action') == 'update_and_restart' or request.GET.get('action') == 'update_and_reload': messages.success(request, _("Export successful!|WireGuard configuration files have been exported to /etc/wireguard/."))