suspend/unsuspend peer with interface reload

This commit is contained in:
Eduardo Silva
2026-02-04 09:43:17 -03:00
parent 246282f217
commit e9983449f8
3 changed files with 86 additions and 12 deletions

View File

@@ -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.'))

View File

@@ -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 <iface> <(wg-quick strip <conf>)
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)

View File

@@ -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/."))