pantalaimon: Add a configuration file for the daemon.

This commit is contained in:
Damir Jelić 2019-05-08 12:33:42 +02:00
parent 65d1401b76
commit 2987589854
3 changed files with 202 additions and 58 deletions

View File

@ -10,4 +10,4 @@ typecheck:
mypy --ignore-missing-imports pantalaimon mypy --ignore-missing-imports pantalaimon
run-local: run-local:
python -m pantalaimon.main https://localhost:8448 --proxy http://localhost:8080 -k --log-level debug python -m pantalaimon.main --log-level debug --config ./contrib/pantalaimon.conf

150
pantalaimon/config.py Normal file
View File

@ -0,0 +1,150 @@
import configparser
import os
from typing import Union
from ipaddress import ip_address, IPv4Address, IPv6Address
from urllib.parse import urlparse, ParseResult
import logbook
import attr
class PanConfigParser(configparser.ConfigParser):
def __init__(self):
super().__init__(
default_section="Default",
defaults={
"SSL": "True",
"ListenAddress": "localhost",
"ListenPort": "8009",
"LogLevel": "warnig",
},
converters={
"address": parse_address,
"url": parse_url,
"loglevel": parse_log_level,
}
)
def parse_address(value):
# type: (str) -> Union[IPv4Address, IPv6Address]
if value == "localhost":
return ip_address("127.0.0.1")
return ip_address(value)
def parse_url(value):
# type: (str) -> ParseResult
value = urlparse(value)
if value.scheme not in ('http', 'https'):
raise ValueError(f"Invalid URL scheme {value.scheme}. "
f"Only HTTP(s) URLs are allowed")
value.port
return value
def parse_log_level(value):
# type: (str) -> logbook
value = value.lower()
if value == "info":
return logbook.INFO
elif value == "warning":
return logbook.WARNING
elif value == "error":
return logbook.ERROR
elif value == "debug":
return logbook.DEBUG
return logbook.WARNING
class PanConfigError(Exception):
"""Pantalaimon configuration error."""
pass
@attr.s
class ServerConfig:
"""Server configuration.
Args:
homeserver (ParseResult): The URL of the Matrix homeserver that we want
to forward requests to.
listen_address (str): The local address where pantalaimon will listen
for connections.
listen_port (int): The port where pantalaimon will listen for
connections.
proxy (ParseResult):
A proxy that the daemon should use when making connections to the
homeserver.
ssl (bool): Enable or disable SSL for the connection between
pantalaimon and the homeserver.
"""
homeserver = attr.ib()
listen_address = attr.ib(type=Union[IPv4Address, IPv6Address])
listen_port = attr.ib(type=int)
proxy = attr.ib(type=str)
ssl = attr.ib(type=bool, default=True)
@attr.s
class PanConfig:
"""Pantalaimon configuration.
Args:
config_path (str): The path where we should search for a configuration
file.
filename (str): The name of the file that we should read.
"""
config_file = attr.ib()
log_level = attr.ib(default=None)
servers = attr.ib(init=False, default=attr.Factory(dict))
def read(self):
"""Read the configuration file.
Raises OSError if the file can't be read or PanConfigError if there is
a syntax error with the config file.
"""
config = PanConfigParser()
try:
config.read(os.path.abspath(self.config_file))
except configparser.Error as e:
raise PanConfigError(e)
if self.log_level is None:
self.log_level = config["Default"].getloglevel("LogLevel")
try:
for section_name, section in config.items():
if section_name == "Default":
continue
homeserver = section.geturl("Homeserver")
listen_address = section.getaddress("ListenAddress")
listen_port = section.getint("ListenPort")
ssl = section.getboolean("SSL")
proxy = section.geturl("Proxy")
server_conf = ServerConfig(
homeserver,
listen_address,
listen_port,
proxy,
ssl
)
self.servers[section_name] = server_conf
except ValueError as e:
raise PanConfigError(e)

View File

@ -7,16 +7,28 @@ from urllib.parse import urlparse
import click import click
import janus import janus
import logbook
from appdirs import user_data_dir from appdirs import user_data_dir, user_config_dir
from logbook import StderrHandler from logbook import StderrHandler
from aiohttp import web from aiohttp import web
from pantalaimon.ui import GlibT from pantalaimon.ui import GlibT
from pantalaimon.log import logger
from pantalaimon.daemon import ProxyDaemon from pantalaimon.daemon import ProxyDaemon
from pantalaimon.config import PanConfig, PanConfigError, parse_log_level
from pantalaimon.log import logger
def create_dirs(data_dir, conf_dir):
try:
os.makedirs(data_dir)
except OSError:
pass
try:
os.makedirs(conf_dir)
except OSError:
pass
async def init(homeserver, http_proxy, ssl, send_queue, recv_queue): async def init(homeserver, http_proxy, ssl, send_queue, recv_queue):
@ -84,77 +96,55 @@ class ipaddress(click.ParamType):
@click.command( @click.command(
help=("pantalaimon is a reverse proxy for matrix homeservers that " help=("pantalaimon is a reverse proxy for matrix homeservers that "
"transparently encrypts and decrypts messages for clients that " "transparently encrypts and decrypts messages for clients that "
"connect to pantalaimon.\n\n" "connect to pantalaimon.")
"HOMESERVER - the homeserver that the daemon should connect to.")
) )
@click.option(
"--proxy",
type=URL(),
default=None,
help="A proxy that will be used to connect to the homeserver."
)
@click.option(
"-k",
"--ssl-insecure/--no-ssl-insecure",
default=False,
help="Disable SSL verification for the homeserver connection."
)
@click.option(
"-l",
"--listen-address",
type=ipaddress(),
default=ip_address("127.0.0.1"),
help=("The listening address for incoming client connections "
"(default: 127.0.0.1)")
)
@click.option(
"-p",
"--listen-port",
type=int,
default=8009,
help="The listening port for incoming client connections (default: 8009)"
)
@click.option("--log-level", type=click.Choice([ @click.option("--log-level", type=click.Choice([
"error", "error",
"warning", "warning",
"info", "info",
"debug" "debug"
]), default="error") ]), default=None)
@click.argument( @click.option("-c", "--config", type=click.Path(exists=True))
"homeserver", @click.pass_context
type=URL(),
)
def main( def main(
proxy, context,
ssl_insecure,
listen_address,
listen_port,
log_level, log_level,
homeserver config
): ):
ssl = None if ssl_insecure is False else False conf_dir = user_config_dir("pantalaimon", "")
data_dir = user_data_dir("pantalaimon", "")
create_dirs(data_dir, conf_dir)
StderrHandler(level=log_level.upper()).push_application() config = config or os.path.join(conf_dir, "pantalaimon.conf")
if log_level == "info": if log_level:
logger.level = logbook.INFO log_level = parse_log_level(log_level)
elif log_level == "warning":
logger.level = logbook.WARNING pan_conf = PanConfig(config, log_level)
elif log_level == "error":
logger.level = logbook.ERROR try:
elif log_level == "debug": pan_conf.read()
logger.level = logbook.DEBUG except (OSError, PanConfigError) as e:
context.fail(e)
if not pan_conf.servers:
context.fail("Homeserver is not configured.")
logger.level = pan_conf.log_level
StderrHandler().push_application()
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
pan_queue = janus.Queue(loop=loop) pan_queue = janus.Queue(loop=loop)
ui_queue = janus.Queue(loop=loop) ui_queue = janus.Queue(loop=loop)
# TODO start the other servers as well
server_conf = list(pan_conf.servers.values())[0]
proxy, app = loop.run_until_complete(init( proxy, app = loop.run_until_complete(init(
homeserver, server_conf.homeserver,
proxy.geturl() if proxy else None, server_conf.proxy.geturl() if server_conf.proxy else None,
ssl, server_conf.ssl,
pan_queue.async_q, pan_queue.async_q,
ui_queue.async_q ui_queue.async_q
)) ))
@ -179,7 +169,11 @@ def main(
home = os.path.expanduser("~") home = os.path.expanduser("~")
os.chdir(home) os.chdir(home)
web.run_app(app, host=str(listen_address), port=listen_port) web.run_app(
app,
host=str(server_conf.listen_address),
port=server_conf.listen_port
)
if __name__ == "__main__": if __name__ == "__main__":