From 568e4f9783063af31799b549b03b91e7d4543fd1 Mon Sep 17 00:00:00 2001 From: Dmitrii Iurco Date: Sat, 6 Jun 2026 12:31:05 -0400 Subject: [PATCH] Fix: prevent wg0.conf truncation when remove_peer splits blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _write_config() was stripping trailing newlines, causing the next add_cell_peer() to create a single-newline separator between [Interface] and [Peer] blocks instead of the required blank line. On the following remove_peer() call, split('\n\n') treated both sections as one block, matched the PublicKey filter, and wrote an empty string — destroying the [Interface] section and reverting to the hardcoded SERVER_ADDRESS fallback. Two-part fix: 1. _write_config() always ends content with a newline 2. remove_peer() normalises single-newline [Peer] headers to blank-line separators before splitting, and refuses to write if [Interface] would be lost Co-Authored-By: Claude Sonnet 4.6 --- api/wireguard_manager.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/api/wireguard_manager.py b/api/wireguard_manager.py index 0807428..524c5f8 100644 --- a/api/wireguard_manager.py +++ b/api/wireguard_manager.py @@ -294,6 +294,8 @@ class WireGuardManager(BaseServiceManager): return self.generate_config() def _write_config(self, content: str): + if content and not content.endswith('\n'): + content += '\n' with open(self._config_file(), 'w') as f: f.write(content) self._syncconf() @@ -805,12 +807,20 @@ class WireGuardManager(BaseServiceManager): """Remove the [Peer] block matching public_key from wg0.conf.""" try: content = self._read_config() - # Split on blank lines between blocks - raw_blocks = ('\n' + content).split('\n\n') + # Normalise to ensure blank-line block separators before splitting. + # Without this, a file written without trailing newline will merge + # [Interface] and the first [Peer] into one block, and the filter + # below would then delete [Interface] together with the peer. + normalised = content.replace('\n[Peer]', '\n\n[Peer]') + raw_blocks = ('\n' + normalised).split('\n\n') new_blocks = [ b for b in raw_blocks if not (f'PublicKey = {public_key}' in b and '[Peer]' in b) ] + # Never write an empty file — that would destroy the [Interface] block. + if not any('[Interface]' in b for b in new_blocks): + logger.error('remove_peer: [Interface] block would be lost — aborting write') + return False self._write_config('\n\n'.join(new_blocks).lstrip('\n')) return True except Exception as e: