100 lines
4.0 KiB
Python
Executable File
100 lines
4.0 KiB
Python
Executable File
#!/usr/bin/python3
|
|
"""Serve the status of the router on its internal address, its external address or both, as web.listen sets.
|
|
|
|
Run by vpn-router-web.service. Users are managed with vpnrouter_users, the module that
|
|
vpn-router-user uses too.
|
|
|
|
The paths it serves and the modules that answer them are described in the package vpnrouter_web.
|
|
|
|
The service first initializes, without listening: it makes the signing key, the OpenID Provider's key
|
|
(vpnrouter_oidc), the refresh tokens' database (vpnrouter_auth) and the web sites' database (vpnrouter_sites), retrying every INIT_RETRY_SECONDS until all are ready and
|
|
logging each failure. Then it listens. Its unit starts it only once wait-for-libpq has found libpq
|
|
in the linker cache, where psycopg looks it up.
|
|
|
|
The service needs nothing else to start and does not end on its own. Every DISCOVERY_SECONDS it
|
|
works out from /etc/vpn-router/vpn-router.json the addresses of the interfaces web.listen names,
|
|
internal, external or both, as general.mode directs, with web.port and web.enabled
|
|
(vpnrouter_net.web_listen), and runs a server on each (vpnrouter_serve.serve): it starts them,
|
|
moves them when an address or the port changes, and stops them while the service is disabled or
|
|
no address is known. Every request reads what it needs when it is made: the signing
|
|
key, the configuration, and the features of the router and the facts shown by /config from
|
|
/run/vpn-router/web.json, which vpn-router-setup writes. Before setup has written that file
|
|
the router has no features, and /config shows no configuration or interfaces.
|
|
"""
|
|
|
|
import os
|
|
import signal
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.realpath(__file__)))
|
|
|
|
import vpnrouter
|
|
import vpnrouter_auth
|
|
import vpnrouter_net
|
|
import vpnrouter_oidc
|
|
import vpnrouter_serve
|
|
import vpnrouter_sites
|
|
from vpnrouter_web.common import CONFIG_FILE, log, read_json
|
|
from vpnrouter_web.handler import Handler
|
|
from vpnrouter_web.shell import editor_listen
|
|
|
|
#: Seconds between two attempts of the initialization.
|
|
INIT_RETRY_SECONDS = 15
|
|
|
|
#: The port when web.port is not set.
|
|
DEFAULT_PORT = 7081
|
|
|
|
#: Seconds between two discoveries of where the service should listen.
|
|
DISCOVERY_SECONDS = 30
|
|
|
|
|
|
def initialize():
|
|
"""Make the signing key, the OpenID Provider's key, the refresh tokens' database and the web sites' database,
|
|
retrying every INIT_RETRY_SECONDS until all are ready."""
|
|
reported = None
|
|
while True:
|
|
phase = 'reading the configuration'
|
|
try:
|
|
cfg = vpnrouter.load_config()
|
|
phase = 'making the signing key'
|
|
vpnrouter_auth.ensure_key()
|
|
phase = 'making the OpenID Provider key'
|
|
vpnrouter_oidc.ensure_key()
|
|
phase = 'creating the token database'
|
|
vpnrouter_auth.provision(cfg)
|
|
phase = 'connecting to the token database'
|
|
with vpnrouter_auth.database(cfg):
|
|
pass
|
|
phase = 'creating the database of the web sites'
|
|
vpnrouter_sites.provision()
|
|
phase = 'connecting to the database of the web sites'
|
|
with vpnrouter_sites.database():
|
|
pass
|
|
except (OSError, SystemExit, vpnrouter_auth.DatabaseError, vpnrouter_sites.DatabaseError) as exc:
|
|
error = f'{phase} failed: {exc.code if isinstance(exc, SystemExit) else exc}'
|
|
if error != reported:
|
|
log(f'{error}; retrying every {INIT_RETRY_SECONDS} seconds')
|
|
reported = error
|
|
time.sleep(INIT_RETRY_SECONDS)
|
|
continue
|
|
log('ready')
|
|
return
|
|
|
|
|
|
def main():
|
|
signal.signal(signal.SIGTERM, lambda *_: sys.exit(0))
|
|
initialize()
|
|
threading.Thread(target=editor_listen, name='editor socket', daemon=True).start()
|
|
try:
|
|
vpnrouter_serve.serve(lambda: vpnrouter_net.web_listen(read_json(CONFIG_FILE), DEFAULT_PORT), Handler, log,
|
|
DISCOVERY_SECONDS)
|
|
except KeyboardInterrupt:
|
|
pass
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|