summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2026-08-06 01:43:02 -0700
committerDavid Vazgenovich Shakaryan <dvshakaryan@gmail.com>2026-08-06 01:48:03 -0700
commit508efbdd70b76511f3f422f434be284d86196659 (patch)
tree0ba8fa925d7dec94b44b0f861701d7516921fe88
parent6220653e3ea18a4a202cb5544abdbd7342b2bcf5 (diff)
downloadwg-genconf-master.tar.gz
wg-genconf-master.tar.xz
improve config format and defaults, add warningsHEADmaster
-rwxr-xr-xwg-genconf.py51
1 files changed, 38 insertions, 13 deletions
diff --git a/wg-genconf.py b/wg-genconf.py
index a8e8886..d02766b 100755
--- a/wg-genconf.py
+++ b/wg-genconf.py
@@ -51,6 +51,11 @@ class Peer(dict):
for x in self.network['subnets']
if not version or x.version == int(version)]
+ def format_string(self, s):
+ return string.Template(s).safe_substitute({
+ 'network': self.network.name,
+ 'peer': self.name})
+
class Interface(collections.UserDict):
_INHERITABLE = [
@@ -58,9 +63,9 @@ class Interface(collections.UserDict):
'fwmark',
'keepalive',
- 'file-prefix',
+ 'keep-empty',
'name-format',
- 'privkey-path',
+ 'systemd-prefix',
]
def __init__(self, data, name, peer):
@@ -90,10 +95,10 @@ class Interface(collections.UserDict):
@functools.cached_property
def formatted_name(self):
- return self.format_string(self.get('name-format', '$network$_if'))
+ return self.format_string(self.get('name-format', 'wg$network$_if'))
def format_string(self, s):
- return string.Template(s).substitute({
+ return string.Template(s).safe_substitute({
'network': self.network.name,
'peer': self.peer.name,
'if': self.name,
@@ -170,6 +175,12 @@ def expand_peerspecs(peer, peerspecs):
def gc_if_data(if_):
+ if not re.fullmatch(r'[A-Za-z0-9+.=_-]{1,15}', if_.formatted_name):
+ print(
+ f'warning: interface {if_.formatted_name}: '
+ f'invalid name, must match [A-Za-z0-9+.=_-]{{1,15}}',
+ file=sys.stderr)
+
if_data = {
'name': if_.formatted_name,
'privkey': get_privkey(if_),
@@ -277,7 +288,7 @@ def gc_if_systemd_netdev(if_data):
def buf_to_file(buf, path, *, mode=None):
- print(f'Creating file {path}')
+ print(f'creating {path}')
if mode:
path = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode=mode)
@@ -288,20 +299,22 @@ def buf_to_file(buf, path, *, mode=None):
def create_if_files(if_, output_dir):
if_data = gc_if_data(if_)
- prefix = os.path.join(output_dir, if_.get('file-prefix', ''))
if if_.get('type') == 'systemd':
+ prefix = if_.get('systemd-prefix', '99')
+ prefix = '' if prefix is None else f'{prefix}-'
+ path = os.path.join(output_dir, f'{prefix}{if_data['name']}')
buf_to_file(
gc_if_systemd_netdev(if_data),
- f'{prefix}{if_.formatted_name}.netdev',
+ f'{path}.netdev',
mode=0o640)
buf_to_file(
gc_if_systemd_network(if_data),
- f'{prefix}{if_.formatted_name}.network')
+ f'{path}.network')
else:
buf_to_file(
gc_if_wgquick(if_data),
- f'{prefix}{if_.formatted_name}.conf')
+ os.path.join(output_dir, f'{if_data['name']}.conf'))
def load_network_peer_interfaces(config, peer):
@@ -310,7 +323,7 @@ def load_network_peer_interfaces(config, peer):
for if_name, if_conf in ifs.items():
# remove self from interface peers
peers = [x for x in if_conf['peers'] if x.get('name') != peer.name]
- if not peers:
+ if not peers and not if_conf.get('keep-empty'):
continue
if_conf['peers'] = peers
@@ -342,12 +355,24 @@ def read_privkey(path):
with open(path) as f:
return f.read().rstrip()
except OSError:
- return 'FIXME'
+ return None
def get_privkey(if_):
- return read_privkey(if_.format_string(os.path.expanduser(
- if_.get('privkey-path', '~/.wg-genconf/${network}.privkey'))))
+ path = if_.peer.get(
+ 'privkey-path', '~/.wg-genconf/${peer}/${network}.privkey')
+ if path:
+ path = if_.peer.format_string(os.path.expanduser(path))
+
+ key = read_privkey(path) if path else None
+ if key is None:
+ print(
+ f'warning: interface {if_.formatted_name}: '
+ f'failed to read privkey at {path}',
+ file=sys.stderr)
+ return 'FIXME'
+
+ return key
def argparse_dir(s):