Coverage for slidge/main.py: 38%
104 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-11-07 05:11 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-11-07 05:11 +0000
1"""
2Slidge can be configured via CLI args, environment variables and/or INI files.
4To use env vars, use this convention: ``--home-dir`` becomes ``HOME_DIR``.
6Everything in ``/etc/slidge/conf.d/*`` is automatically used.
7To use a plugin-specific INI file, put it in another dir,
8and launch slidge with ``-c /path/to/plugin-specific.conf``.
9Use the long version of the CLI arg without the double dash prefix inside this
10INI file, eg ``debug=true``.
12An example configuration file is available at
13https://git.sr.ht/~nicoco/slidge/tree/master/item/dev/confs/slidge-example.ini
14"""
16import asyncio
17import importlib
18import inspect
19import logging
20import os
21import re
22import signal
23from pathlib import Path
25import configargparse
27from slidge import BaseGateway
28from slidge.__version__ import __version__
29from slidge.core import config
30from slidge.core.pubsub import PepAvatar, PepNick
31from slidge.db import SlidgeStore
32from slidge.db.avatar import avatar_cache
33from slidge.db.meta import get_engine
34from slidge.migration import migrate
35from slidge.util.conf import ConfigModule
38class MainConfig(ConfigModule):
39 def update_dynamic_defaults(self, args):
40 # force=True is needed in case we call a logger before this is reached,
41 # or basicConfig has no effect
42 logging.basicConfig(
43 level=args.loglevel,
44 filename=args.log_file,
45 force=True,
46 format=args.log_format,
47 )
49 if args.home_dir is None:
50 args.home_dir = Path("/var/lib/slidge") / str(args.jid)
52 if args.user_jid_validator is None:
53 args.user_jid_validator = ".*@" + re.escape(args.server)
55 if args.db_url is None:
56 args.db_url = f"sqlite:///{args.home_dir}/slidge.sqlite"
59class SigTermInterrupt(Exception):
60 pass
63def get_configurator():
64 p = configargparse.ArgumentParser(
65 default_config_files=os.getenv(
66 "SLIDGE_CONF_DIR", "/etc/slidge/conf.d/*.conf"
67 ).split(":"),
68 description=__doc__,
69 )
70 p.add_argument(
71 "-c",
72 "--config",
73 help="Path to a INI config file.",
74 env_var="SLIDGE_CONFIG",
75 is_config_file=True,
76 )
77 p.add_argument(
78 "-q",
79 "--quiet",
80 help="loglevel=WARNING",
81 action="store_const",
82 dest="loglevel",
83 const=logging.WARNING,
84 default=logging.INFO,
85 env_var="SLIDGE_QUIET",
86 )
87 p.add_argument(
88 "-d",
89 "--debug",
90 help="loglevel=DEBUG",
91 action="store_const",
92 dest="loglevel",
93 const=logging.DEBUG,
94 env_var="SLIDGE_DEBUG",
95 )
96 p.add_argument(
97 "--version",
98 action="version",
99 version=f"%(prog)s {__version__}",
100 )
101 configurator = MainConfig(config, p)
102 return configurator
105def get_parser():
106 return get_configurator().parser
109def configure():
110 configurator = get_configurator()
111 args, unknown_argv = configurator.set_conf()
113 if not (h := config.HOME_DIR).exists():
114 logging.info("Creating directory '%s'", h)
115 os.makedirs(h)
117 config.UPLOAD_REQUESTER = config.UPLOAD_REQUESTER or config.JID.bare
119 return unknown_argv
122def handle_sigterm(_signum, _frame):
123 logging.info("Caught SIGTERM")
124 raise SigTermInterrupt
127def main():
128 signal.signal(signal.SIGTERM, handle_sigterm)
130 unknown_argv = configure()
131 logging.info("Starting slidge version %s", __version__)
133 legacy_module = importlib.import_module(config.LEGACY_MODULE)
134 logging.debug("Legacy module: %s", dir(legacy_module))
135 logging.info(
136 "Starting legacy module: '%s' version %s",
137 config.LEGACY_MODULE,
138 getattr(legacy_module, "__version__", "No version"),
139 )
141 if plugin_config_obj := getattr(
142 legacy_module, "config", getattr(legacy_module, "Config", None)
143 ):
144 # If the legacy module has default parameters that depend on dynamic defaults
145 # of the slidge main config, it needs to be refreshed at this point, because
146 # now the dynamic defaults are set.
147 if inspect.ismodule(plugin_config_obj):
148 importlib.reload(plugin_config_obj)
149 logging.debug("Found a config object in plugin: %r", plugin_config_obj)
150 ConfigModule.ENV_VAR_PREFIX += (
151 f"_{config.LEGACY_MODULE.split('.')[-1].upper()}_"
152 )
153 logging.debug("Env var prefix: %s", ConfigModule.ENV_VAR_PREFIX)
154 ConfigModule(plugin_config_obj).set_conf(unknown_argv)
155 else:
156 if unknown_argv:
157 raise RuntimeError("Some arguments have not been recognized", unknown_argv)
159 migrate()
161 store = SlidgeStore(get_engine(config.DB_URL))
162 BaseGateway.store = store
163 gateway: BaseGateway = BaseGateway.get_unique_subclass()()
164 avatar_cache.store = gateway.store.avatars
165 avatar_cache.set_dir(config.HOME_DIR / "slidge_avatars_v3")
167 PepAvatar.store = gateway.store
168 PepNick.contact_store = gateway.store.contacts
170 gateway.connect()
172 return_code = 0
173 try:
174 gateway.loop.run_forever()
175 except KeyboardInterrupt:
176 logging.debug("Received SIGINT")
177 except SigTermInterrupt:
178 logging.debug("Received SIGTERM")
179 except SystemExit as e:
180 return_code = e.code # type: ignore
181 logging.debug("Exit called")
182 except Exception as e:
183 return_code = 2
184 logging.exception("Exception in __main__")
185 logging.exception(e)
186 finally:
187 if gateway.has_crashed:
188 if return_code != 0:
189 logging.warning("Return code has been set twice. Please report this.")
190 return_code = 3
191 if gateway.is_connected():
192 logging.debug("Gateway is connected, cleaning up")
193 gateway.loop.run_until_complete(asyncio.gather(*gateway.shutdown()))
194 gateway.disconnect()
195 gateway.loop.run_until_complete(gateway.disconnected)
196 else:
197 logging.debug("Gateway is not connected, no need to clean up")
198 avatar_cache.close()
199 gateway.loop.run_until_complete(gateway.http.close())
200 logging.info("Successful clean shut down")
201 logging.debug("Exiting with code %s", return_code)
202 exit(return_code)