Fix: prevent wg0.conf truncation when remove_peer splits blocks
Unit Tests / test (push) Successful in 7m46s

_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 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 12:31:05 -04:00
parent 26576e1124
commit 568e4f9783
+12 -2
View File
@@ -294,6 +294,8 @@ class WireGuardManager(BaseServiceManager):
return self.generate_config() return self.generate_config()
def _write_config(self, content: str): def _write_config(self, content: str):
if content and not content.endswith('\n'):
content += '\n'
with open(self._config_file(), 'w') as f: with open(self._config_file(), 'w') as f:
f.write(content) f.write(content)
self._syncconf() self._syncconf()
@@ -805,12 +807,20 @@ class WireGuardManager(BaseServiceManager):
"""Remove the [Peer] block matching public_key from wg0.conf.""" """Remove the [Peer] block matching public_key from wg0.conf."""
try: try:
content = self._read_config() content = self._read_config()
# Split on blank lines between blocks # Normalise to ensure blank-line block separators before splitting.
raw_blocks = ('\n' + content).split('\n\n') # 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 = [ new_blocks = [
b for b in raw_blocks b for b in raw_blocks
if not (f'PublicKey = {public_key}' in b and '[Peer]' in b) 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')) self._write_config('\n\n'.join(new_blocks).lstrip('\n'))
return True return True
except Exception as e: except Exception as e: