feature: vpn-config-edit shows and changes the router's configuration.
Release / release (push) Successful in 19s
Release / release (push) Successful in 19s
This commit is contained in:
1 parent
650896131d
commit
83c1c50d8d
4 files changed
+161
No files matched your search
@@ -240,6 +240,24 @@ vpn-router-setup` applies a changed configuration while Docker runs. With `docke
|
||||
`on`, the service fails when Docker is not installed or its daemon is not running; with
|
||||
`off` it removes the rules.
|
||||
|
||||
## Editing the configuration
|
||||
|
||||
`vpn-config-edit`, run as root, shows and changes `/etc/vpn-router/vpn-router.json`:
|
||||
|
||||
```sh
|
||||
vpn-config-edit show
|
||||
vpn-config-edit set web.status_content standard
|
||||
vpn-config-edit set web.port 7082
|
||||
vpn-config-edit set web.trusted_proxies 127.0.0.1,10.0.0.4
|
||||
```
|
||||
|
||||
`show` prints the file as indented JSON and does not change it. `set` takes a setting as
|
||||
`section.option` and converts the value to the setting's type: a whole number, `true` or
|
||||
`false`, a list separated by commas (an empty value is an empty list), or a string; an unknown
|
||||
setting is refused. It checks the whole configuration as `vpn-router-setup` does, and only then
|
||||
writes the file, indented, with mode 0600, and reminds that `vpn-router-setup` applies it. A
|
||||
refused request exits with status 2, any other failure with 1.
|
||||
|
||||
## Applying changes
|
||||
|
||||
```sh
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
vpn-router (0.15.0-1) unstable; urgency=medium
|
||||
|
||||
feature: vpn-config-edit shows and changes the router's configuration.
|
||||
|
||||
* vpn-config-edit show prints /etc/vpn-router/vpn-router.json as
|
||||
indented JSON.
|
||||
* vpn-config-edit set SECTION.OPTION VALUE converts the value to the
|
||||
setting's type, checks the whole configuration as vpn-router-setup
|
||||
does, writes the file indented with mode 0600, and reminds that
|
||||
vpn-router-setup applies it.
|
||||
|
||||
-- Slawomir Koszewski <slawek@koszewscy.waw.pl> Sun, 25 Oct 2026 18:00:00 +0200
|
||||
|
||||
vpn-router (0.14.1-1) unstable; urgency=medium
|
||||
|
||||
fix: web.status_content is basic by default.
|
||||
|
||||
@@ -9,6 +9,7 @@ src/usr/lib/vpn-router/openvpn-same-user usr/lib/vpn-router/
|
||||
src/usr/sbin/vpn-router-user usr/sbin/
|
||||
src/usr/sbin/vpn-router-group usr/sbin/
|
||||
src/usr/sbin/vpn-router-token usr/sbin/
|
||||
src/usr/sbin/vpn-config-edit usr/sbin/
|
||||
src/usr/lib/vpn-router/simple-ca usr/lib/vpn-router/
|
||||
src/usr/lib/vpn-router/configure usr/lib/vpn-router/
|
||||
src/usr/lib/vpn-router/docker-rules usr/lib/vpn-router/
|
||||
|
||||
Executable
+129
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/python3
|
||||
"""Show and change the router's configuration, /etc/vpn-router/vpn-router.json.
|
||||
|
||||
show prints the file as indented JSON; the file is not changed. set takes a setting as
|
||||
section.option, such as web.port, and a value, which it converts to the type the package's
|
||||
schema (vpnrouter.SCHEMA) gives the setting: a whole number, true or false, a list of strings
|
||||
separated by commas (an empty value is an empty list), or a string. The configuration with the
|
||||
new value is checked as vpn-router-setup checks it, and only then written, indented, with mode
|
||||
0600; vpn-router-setup applies it at its next run, which set reminds of. A failure is a message
|
||||
on standard error; the exit status is 2 for an invalid request and 1 for any other failure.
|
||||
-h or --help shows the options.
|
||||
|
||||
vpn-config-edit show
|
||||
vpn-config-edit set SECTION.OPTION VALUE
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
sys.path.insert(0, '/usr/lib/vpn-router')
|
||||
|
||||
import vpnrouter
|
||||
|
||||
|
||||
class Failure(Exception):
|
||||
"""A request that cannot be done; status is 2 invalid, 1 other."""
|
||||
|
||||
def __init__(self, message, status=1):
|
||||
super().__init__(message)
|
||||
self.status = status
|
||||
|
||||
|
||||
class Parser(argparse.ArgumentParser):
|
||||
"""An argument parser whose errors are Failures with status 2."""
|
||||
|
||||
def error(self, message):
|
||||
raise Failure(f'{message}; see {self.prog} --help', 2)
|
||||
|
||||
|
||||
def read():
|
||||
"""The settings in the configuration file, without the defaults; {} when the file does not exist."""
|
||||
try:
|
||||
data = json.loads(vpnrouter.CONFIG_FILE.read_text(encoding='utf-8'))
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
except ValueError as exc:
|
||||
raise Failure(f'cannot parse {vpnrouter.CONFIG_FILE}: {exc}')
|
||||
if not isinstance(data, dict) or not all(isinstance(options, dict) for options in data.values()):
|
||||
raise Failure(f'cannot parse {vpnrouter.CONFIG_FILE}: it is not an object of sections')
|
||||
return data
|
||||
|
||||
|
||||
def converted(key, text):
|
||||
"""The value of text for the setting key, section.option, of the type the schema gives it."""
|
||||
section, _, option = key.partition('.')
|
||||
if section not in vpnrouter.SCHEMA or option not in vpnrouter.SCHEMA[section]:
|
||||
raise Failure(f'{key}: unknown setting; settings are section.option, such as web.port', 2)
|
||||
kind = type(vpnrouter.SCHEMA[section][option])
|
||||
if kind is bool:
|
||||
if text not in ('true', 'false'):
|
||||
raise Failure(f'{key} takes true or false', 2)
|
||||
return text == 'true'
|
||||
if kind is int:
|
||||
try:
|
||||
return int(text)
|
||||
except ValueError:
|
||||
raise Failure(f'{key} takes a whole number', 2)
|
||||
if kind is list:
|
||||
return [item.strip() for item in text.split(',') if item.strip()]
|
||||
return text
|
||||
|
||||
|
||||
def write(data):
|
||||
"""Write the settings, indented, with mode 0600, replacing the file in one step."""
|
||||
directory = vpnrouter.CONFIG_FILE.parent
|
||||
descriptor, name = tempfile.mkstemp(dir=directory, prefix='.vpn-router.json.')
|
||||
try:
|
||||
with os.fdopen(descriptor, 'w', encoding='utf-8') as handle:
|
||||
handle.write(json.dumps(data, indent=2) + '\n')
|
||||
os.chmod(name, 0o600)
|
||||
os.replace(name, vpnrouter.CONFIG_FILE)
|
||||
except OSError:
|
||||
os.unlink(name)
|
||||
raise
|
||||
|
||||
|
||||
def main(argv):
|
||||
try:
|
||||
parser = Parser(prog='vpn-config-edit', description='Show and change the router configuration.')
|
||||
commands = parser.add_subparsers(dest='command', required=True, parser_class=Parser)
|
||||
commands.add_parser('show', help='print the configuration as indented JSON')
|
||||
setter = commands.add_parser('set', help='set a setting and write the configuration')
|
||||
setter.add_argument('key', metavar='SECTION.OPTION', help='the setting, such as web.port')
|
||||
setter.add_argument('value', metavar='VALUE', help='its value; a list separated by commas')
|
||||
options = parser.parse_args(argv)
|
||||
|
||||
data = read()
|
||||
if options.command == 'show':
|
||||
print(json.dumps(data, indent=2))
|
||||
return 0
|
||||
|
||||
section, _, option = options.key.partition('.')
|
||||
value = converted(options.key, options.value)
|
||||
data.setdefault(section, {})[option] = value
|
||||
# The configuration as vpn-router-setup reads it: the defaults with the file's settings.
|
||||
candidate = {name: dict(defaults) for name, defaults in vpnrouter.SCHEMA.items()}
|
||||
for name, settings in data.items():
|
||||
candidate.setdefault(name, {}).update(settings)
|
||||
try:
|
||||
vpnrouter.validate(candidate)
|
||||
except vpnrouter.ConfigError as exc:
|
||||
raise Failure(str(exc), 2)
|
||||
write(data)
|
||||
except Failure as failure:
|
||||
print(f'vpn-config-edit: {failure}', file=sys.stderr)
|
||||
return failure.status
|
||||
except OSError as exc:
|
||||
print(f'vpn-config-edit: {exc}', file=sys.stderr)
|
||||
return 1
|
||||
print(f'{options.key} set to {json.dumps(value)} in {vpnrouter.CONFIG_FILE}.')
|
||||
print('Apply it with: systemctl restart vpn-router-setup')
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
Reference in new issue
Block a user