Coverage for slidge/main.py: 38%

104 statements  

« 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. 

3 

4To use env vars, use this convention: ``--home-dir`` becomes ``HOME_DIR``. 

5 

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``. 

11 

12An example configuration file is available at 

13https://git.sr.ht/~nicoco/slidge/tree/master/item/dev/confs/slidge-example.ini 

14""" 

15 

16import asyncio 

17import importlib 

18import inspect 

19import logging 

20import os 

21import re 

22import signal 

23from pathlib import Path 

24 

25import configargparse 

26 

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 

36 

37 

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 ) 

48 

49 if args.home_dir is None: 

50 args.home_dir = Path("/var/lib/slidge") / str(args.jid) 

51 

52 if args.user_jid_validator is None: 

53 args.user_jid_validator = ".*@" + re.escape(args.server) 

54 

55 if args.db_url is None: 

56 args.db_url = f"sqlite:///{args.home_dir}/slidge.sqlite" 

57 

58 

59class SigTermInterrupt(Exception): 

60 pass 

61 

62 

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 

103 

104 

105def get_parser(): 

106 return get_configurator().parser 

107 

108 

109def configure(): 

110 configurator = get_configurator() 

111 args, unknown_argv = configurator.set_conf() 

112 

113 if not (h := config.HOME_DIR).exists(): 

114 logging.info("Creating directory '%s'", h) 

115 os.makedirs(h) 

116 

117 config.UPLOAD_REQUESTER = config.UPLOAD_REQUESTER or config.JID.bare 

118 

119 return unknown_argv 

120 

121 

122def handle_sigterm(_signum, _frame): 

123 logging.info("Caught SIGTERM") 

124 raise SigTermInterrupt 

125 

126 

127def main(): 

128 signal.signal(signal.SIGTERM, handle_sigterm) 

129 

130 unknown_argv = configure() 

131 logging.info("Starting slidge version %s", __version__) 

132 

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 ) 

140 

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) 

158 

159 migrate() 

160 

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") 

166 

167 PepAvatar.store = gateway.store 

168 PepNick.contact_store = gateway.store.contacts 

169 

170 gateway.connect() 

171 

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)