mmrelay 1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mmrelay might be problematic. Click here for more details.
- mmrelay/__init__.py +9 -0
- mmrelay/cli.py +384 -0
- mmrelay/config.py +218 -0
- mmrelay/config_checker.py +133 -0
- mmrelay/db_utils.py +309 -0
- mmrelay/log_utils.py +107 -0
- mmrelay/main.py +281 -0
- mmrelay/matrix_utils.py +754 -0
- mmrelay/meshtastic_utils.py +569 -0
- mmrelay/plugin_loader.py +336 -0
- mmrelay/plugins/__init__.py +3 -0
- mmrelay/plugins/base_plugin.py +212 -0
- mmrelay/plugins/debug_plugin.py +17 -0
- mmrelay/plugins/drop_plugin.py +120 -0
- mmrelay/plugins/health_plugin.py +64 -0
- mmrelay/plugins/help_plugin.py +55 -0
- mmrelay/plugins/map_plugin.py +323 -0
- mmrelay/plugins/mesh_relay_plugin.py +134 -0
- mmrelay/plugins/nodes_plugin.py +92 -0
- mmrelay/plugins/ping_plugin.py +118 -0
- mmrelay/plugins/telemetry_plugin.py +179 -0
- mmrelay/plugins/weather_plugin.py +208 -0
- mmrelay/setup_utils.py +263 -0
- mmrelay-1.0.dist-info/METADATA +160 -0
- mmrelay-1.0.dist-info/RECORD +29 -0
- mmrelay-1.0.dist-info/WHEEL +5 -0
- mmrelay-1.0.dist-info/entry_points.txt +2 -0
- mmrelay-1.0.dist-info/licenses/LICENSE +21 -0
- mmrelay-1.0.dist-info/top_level.txt +1 -0
mmrelay/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Meshtastic Matrix Relay - Bridge between Meshtastic mesh networks and Matrix chat rooms.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
# Get version from environment variable if available (set by GitHub Actions)
|
|
8
|
+
# Otherwise, use a default version
|
|
9
|
+
__version__ = os.environ.get("GITHUB_REF_NAME", "1.0.0")
|
mmrelay/cli.py
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface handling for the Meshtastic Matrix Relay.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
from yaml.loader import SafeLoader
|
|
11
|
+
|
|
12
|
+
# Import version from package
|
|
13
|
+
from mmrelay import __version__
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def parse_arguments():
|
|
17
|
+
"""
|
|
18
|
+
Parse command-line arguments.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
argparse.Namespace: The parsed command-line arguments
|
|
22
|
+
"""
|
|
23
|
+
parser = argparse.ArgumentParser(
|
|
24
|
+
description="Meshtastic Matrix Relay - Bridge between Meshtastic and Matrix"
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument("--config", help="Path to config file", default=None)
|
|
27
|
+
parser.add_argument(
|
|
28
|
+
"--data-dir",
|
|
29
|
+
help="Base directory for all data (logs, database, plugins)",
|
|
30
|
+
default=None,
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--log-level",
|
|
34
|
+
choices=["error", "warning", "info", "debug"],
|
|
35
|
+
help="Set logging level",
|
|
36
|
+
default=None,
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
"--logfile",
|
|
40
|
+
help="Path to log file (can be overridden by --data-dir)",
|
|
41
|
+
default=None,
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument("--version", action="store_true", help="Show version and exit")
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--generate-config",
|
|
46
|
+
action="store_true",
|
|
47
|
+
help="Generate a sample config.yaml file",
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument(
|
|
50
|
+
"--install-service",
|
|
51
|
+
action="store_true",
|
|
52
|
+
help="Install or update the systemd user service",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument(
|
|
55
|
+
"--check-config",
|
|
56
|
+
action="store_true",
|
|
57
|
+
help="Check if the configuration file is valid",
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# Windows-specific handling for backward compatibility
|
|
61
|
+
# On Windows, add a positional argument for the config file path
|
|
62
|
+
if sys.platform == "win32":
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"config_path", nargs="?", help=argparse.SUPPRESS, default=None
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
args = parser.parse_args()
|
|
68
|
+
|
|
69
|
+
# If on Windows and a positional config path is provided but --config is not, use the positional one
|
|
70
|
+
if (
|
|
71
|
+
sys.platform == "win32"
|
|
72
|
+
and hasattr(args, "config_path")
|
|
73
|
+
and args.config_path
|
|
74
|
+
and not args.config
|
|
75
|
+
):
|
|
76
|
+
args.config = args.config_path
|
|
77
|
+
# Print a deprecation warning
|
|
78
|
+
print("Warning: Using positional argument for config file is deprecated.")
|
|
79
|
+
print(f"Please use --config {args.config_path} instead.")
|
|
80
|
+
# Remove the positional argument from sys.argv to avoid issues with other argument parsers
|
|
81
|
+
if args.config_path in sys.argv:
|
|
82
|
+
sys.argv.remove(args.config_path)
|
|
83
|
+
|
|
84
|
+
return args
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_version():
|
|
88
|
+
"""
|
|
89
|
+
Returns the current version of the application.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
str: The version string
|
|
93
|
+
"""
|
|
94
|
+
return __version__
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def check_config(args=None):
|
|
98
|
+
"""
|
|
99
|
+
Check if the configuration file is valid.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
args: The parsed command-line arguments
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
bool: True if the configuration is valid, False otherwise.
|
|
106
|
+
"""
|
|
107
|
+
from mmrelay.config import get_config_paths
|
|
108
|
+
|
|
109
|
+
# If args is None, parse them now
|
|
110
|
+
if args is None:
|
|
111
|
+
args = parse_arguments()
|
|
112
|
+
|
|
113
|
+
config_paths = get_config_paths(args)
|
|
114
|
+
config_path = None
|
|
115
|
+
|
|
116
|
+
# Try each config path in order until we find one that exists
|
|
117
|
+
for path in config_paths:
|
|
118
|
+
if os.path.isfile(path):
|
|
119
|
+
config_path = path
|
|
120
|
+
print(f"Found configuration file at: {config_path}")
|
|
121
|
+
try:
|
|
122
|
+
with open(config_path, "r") as f:
|
|
123
|
+
config = yaml.load(f, Loader=SafeLoader)
|
|
124
|
+
|
|
125
|
+
# Check if config is empty
|
|
126
|
+
if not config:
|
|
127
|
+
print("Error: Configuration file is empty or invalid")
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
# Check matrix section
|
|
131
|
+
if "matrix" not in config:
|
|
132
|
+
print("Error: Missing 'matrix' section in config")
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
matrix_section = config["matrix"]
|
|
136
|
+
required_matrix_fields = ["homeserver", "access_token", "bot_user_id"]
|
|
137
|
+
missing_matrix_fields = [
|
|
138
|
+
field
|
|
139
|
+
for field in required_matrix_fields
|
|
140
|
+
if field not in matrix_section
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
if missing_matrix_fields:
|
|
144
|
+
print(
|
|
145
|
+
f"Error: Missing required fields in 'matrix' section: {', '.join(missing_matrix_fields)}"
|
|
146
|
+
)
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
# Check matrix_rooms section
|
|
150
|
+
if "matrix_rooms" not in config or not config["matrix_rooms"]:
|
|
151
|
+
print("Error: Missing or empty 'matrix_rooms' section in config")
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
if not isinstance(config["matrix_rooms"], list):
|
|
155
|
+
print("Error: 'matrix_rooms' must be a list")
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
for i, room in enumerate(config["matrix_rooms"]):
|
|
159
|
+
if not isinstance(room, dict):
|
|
160
|
+
print(
|
|
161
|
+
f"Error: Room {i+1} in 'matrix_rooms' must be a dictionary"
|
|
162
|
+
)
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
if "id" not in room:
|
|
166
|
+
print(
|
|
167
|
+
f"Error: Room {i+1} in 'matrix_rooms' is missing the 'id' field"
|
|
168
|
+
)
|
|
169
|
+
return False
|
|
170
|
+
|
|
171
|
+
# Check meshtastic section
|
|
172
|
+
if "meshtastic" not in config:
|
|
173
|
+
print("Error: Missing 'meshtastic' section in config")
|
|
174
|
+
return False
|
|
175
|
+
|
|
176
|
+
meshtastic_section = config["meshtastic"]
|
|
177
|
+
if "connection_type" not in meshtastic_section:
|
|
178
|
+
print("Error: Missing 'connection_type' in 'meshtastic' section")
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
connection_type = meshtastic_section["connection_type"]
|
|
182
|
+
if connection_type not in ["tcp", "serial", "ble", "network"]:
|
|
183
|
+
print(
|
|
184
|
+
f"Error: Invalid 'connection_type': {connection_type}. Must be 'tcp', 'serial', or 'ble'"
|
|
185
|
+
)
|
|
186
|
+
return False
|
|
187
|
+
|
|
188
|
+
# Check for deprecated connection_type
|
|
189
|
+
if connection_type == "network":
|
|
190
|
+
print(
|
|
191
|
+
"\nWarning: 'network' connection_type is deprecated. Please use 'tcp' instead."
|
|
192
|
+
)
|
|
193
|
+
print(
|
|
194
|
+
"See ANNOUNCEMENT.md for more information about deprecated options.\n"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# Check connection-specific fields
|
|
198
|
+
if (
|
|
199
|
+
connection_type == "serial"
|
|
200
|
+
and "serial_port" not in meshtastic_section
|
|
201
|
+
):
|
|
202
|
+
print("Error: Missing 'serial_port' for 'serial' connection type")
|
|
203
|
+
return False
|
|
204
|
+
|
|
205
|
+
if (
|
|
206
|
+
connection_type in ["tcp", "network"]
|
|
207
|
+
and "host" not in meshtastic_section
|
|
208
|
+
):
|
|
209
|
+
print("Error: Missing 'host' for 'tcp' connection type")
|
|
210
|
+
return False
|
|
211
|
+
|
|
212
|
+
if connection_type == "ble" and "ble_address" not in meshtastic_section:
|
|
213
|
+
print("Error: Missing 'ble_address' for 'ble' connection type")
|
|
214
|
+
return False
|
|
215
|
+
|
|
216
|
+
# Check for deprecated db section
|
|
217
|
+
if "db" in config:
|
|
218
|
+
print(
|
|
219
|
+
"\nWarning: 'db' section is deprecated. Please use 'database' instead."
|
|
220
|
+
)
|
|
221
|
+
print(
|
|
222
|
+
"See ANNOUNCEMENT.md for more information about deprecated options.\n"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
print("Configuration file is valid!")
|
|
226
|
+
return True
|
|
227
|
+
except yaml.YAMLError as e:
|
|
228
|
+
print(f"Error parsing YAML in {config_path}: {e}")
|
|
229
|
+
return False
|
|
230
|
+
except Exception as e:
|
|
231
|
+
print(f"Error checking configuration: {e}")
|
|
232
|
+
return False
|
|
233
|
+
|
|
234
|
+
print("Error: No configuration file found in any of the following locations:")
|
|
235
|
+
for path in config_paths:
|
|
236
|
+
print(f" - {path}")
|
|
237
|
+
print("\nRun 'mmrelay --generate-config' to generate a sample configuration file.")
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def main():
|
|
242
|
+
"""Entry point for CLI commands.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
int: Exit code (0 for success, non-zero for failure)
|
|
246
|
+
"""
|
|
247
|
+
args = parse_arguments()
|
|
248
|
+
|
|
249
|
+
# Handle --check-config
|
|
250
|
+
if args.check_config:
|
|
251
|
+
return 0 if check_config(args) else 1
|
|
252
|
+
|
|
253
|
+
# Handle --install-service
|
|
254
|
+
if args.install_service:
|
|
255
|
+
from mmrelay.setup_utils import install_service
|
|
256
|
+
|
|
257
|
+
return 0 if install_service() else 1
|
|
258
|
+
|
|
259
|
+
# Handle --generate-config
|
|
260
|
+
if args.generate_config:
|
|
261
|
+
return 0 if generate_sample_config() else 1
|
|
262
|
+
|
|
263
|
+
# Handle --version
|
|
264
|
+
if args.version:
|
|
265
|
+
print(f"mmrelay {get_version()}")
|
|
266
|
+
return 0
|
|
267
|
+
|
|
268
|
+
# If no command was specified, run the main functionality
|
|
269
|
+
from mmrelay.main import run_main
|
|
270
|
+
|
|
271
|
+
return run_main(args)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == "__main__":
|
|
275
|
+
import sys
|
|
276
|
+
|
|
277
|
+
sys.exit(main())
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def handle_cli_commands(args):
|
|
281
|
+
"""Handle CLI commands like --generate-config, --install-service, and --check-config.
|
|
282
|
+
|
|
283
|
+
Args:
|
|
284
|
+
args: The parsed command-line arguments
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
bool: True if a command was handled and the program should exit,
|
|
288
|
+
False if normal execution should continue.
|
|
289
|
+
"""
|
|
290
|
+
# Handle --version
|
|
291
|
+
if args.version:
|
|
292
|
+
print(f"mmrelay {get_version()}")
|
|
293
|
+
return True
|
|
294
|
+
|
|
295
|
+
# Handle --install-service
|
|
296
|
+
if args.install_service:
|
|
297
|
+
from mmrelay.setup_utils import install_service
|
|
298
|
+
|
|
299
|
+
success = install_service()
|
|
300
|
+
import sys
|
|
301
|
+
|
|
302
|
+
sys.exit(0 if success else 1)
|
|
303
|
+
|
|
304
|
+
# Handle --generate-config
|
|
305
|
+
if args.generate_config:
|
|
306
|
+
if generate_sample_config():
|
|
307
|
+
# Exit with success if config was generated
|
|
308
|
+
return True
|
|
309
|
+
else:
|
|
310
|
+
# Exit with error if config generation failed
|
|
311
|
+
import sys
|
|
312
|
+
|
|
313
|
+
sys.exit(1)
|
|
314
|
+
|
|
315
|
+
# Handle --check-config
|
|
316
|
+
if args.check_config:
|
|
317
|
+
import sys
|
|
318
|
+
|
|
319
|
+
sys.exit(0 if check_config() else 1)
|
|
320
|
+
|
|
321
|
+
# No commands were handled
|
|
322
|
+
return False
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def generate_sample_config():
|
|
326
|
+
"""Generate a sample config.yaml file.
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
bool: True if the config was generated successfully, False otherwise.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
import shutil
|
|
333
|
+
|
|
334
|
+
from mmrelay.config import get_config_paths
|
|
335
|
+
|
|
336
|
+
# Get the first config path (highest priority)
|
|
337
|
+
config_paths = get_config_paths()
|
|
338
|
+
|
|
339
|
+
# Check if any config file exists
|
|
340
|
+
existing_config = None
|
|
341
|
+
for path in config_paths:
|
|
342
|
+
if os.path.isfile(path):
|
|
343
|
+
existing_config = path
|
|
344
|
+
break
|
|
345
|
+
|
|
346
|
+
if existing_config:
|
|
347
|
+
print(f"A config file already exists at: {existing_config}")
|
|
348
|
+
print(
|
|
349
|
+
"Use --config to specify a different location if you want to generate a new one."
|
|
350
|
+
)
|
|
351
|
+
return False
|
|
352
|
+
|
|
353
|
+
# No config file exists, generate one in the first location
|
|
354
|
+
target_path = config_paths[0]
|
|
355
|
+
|
|
356
|
+
# Ensure the directory exists
|
|
357
|
+
os.makedirs(os.path.dirname(target_path), exist_ok=True)
|
|
358
|
+
|
|
359
|
+
# Try to find the sample config file
|
|
360
|
+
# First, check in the package directory
|
|
361
|
+
package_dir = os.path.dirname(__file__)
|
|
362
|
+
sample_config_path = os.path.join(
|
|
363
|
+
os.path.dirname(os.path.dirname(package_dir)), "sample_config.yaml"
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# If not found, try the repository root
|
|
367
|
+
if not os.path.exists(sample_config_path):
|
|
368
|
+
repo_root = os.path.dirname(os.path.dirname(__file__))
|
|
369
|
+
sample_config_path = os.path.join(repo_root, "sample_config.yaml")
|
|
370
|
+
|
|
371
|
+
# If still not found, try the current directory
|
|
372
|
+
if not os.path.exists(sample_config_path):
|
|
373
|
+
sample_config_path = os.path.join(os.getcwd(), "sample_config.yaml")
|
|
374
|
+
|
|
375
|
+
if os.path.exists(sample_config_path):
|
|
376
|
+
shutil.copy(sample_config_path, target_path)
|
|
377
|
+
print(f"Generated sample config file at: {target_path}")
|
|
378
|
+
print(
|
|
379
|
+
"\nEdit this file with your Matrix and Meshtastic settings before running mmrelay."
|
|
380
|
+
)
|
|
381
|
+
return True
|
|
382
|
+
else:
|
|
383
|
+
print("Error: Could not find sample_config.yaml")
|
|
384
|
+
return False
|
mmrelay/config.py
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import platformdirs
|
|
6
|
+
import yaml
|
|
7
|
+
from yaml.loader import SafeLoader
|
|
8
|
+
|
|
9
|
+
# Define custom base directory for Unix systems
|
|
10
|
+
APP_NAME = "mmrelay"
|
|
11
|
+
APP_AUTHOR = None # No author directory
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Global variable to store the custom data directory
|
|
15
|
+
custom_data_dir = None
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Custom base directory for Unix systems
|
|
19
|
+
def get_base_dir():
|
|
20
|
+
"""Returns the base directory for all application files.
|
|
21
|
+
|
|
22
|
+
If a custom data directory has been set via --data-dir, that will be used.
|
|
23
|
+
Otherwise, defaults to ~/.mmrelay on Unix systems or the appropriate
|
|
24
|
+
platformdirs location on Windows.
|
|
25
|
+
"""
|
|
26
|
+
# If a custom data directory has been set, use that
|
|
27
|
+
if custom_data_dir:
|
|
28
|
+
return custom_data_dir
|
|
29
|
+
|
|
30
|
+
if sys.platform in ["linux", "darwin"]:
|
|
31
|
+
# Use ~/.mmrelay for Linux and Mac
|
|
32
|
+
return os.path.expanduser(os.path.join("~", "." + APP_NAME))
|
|
33
|
+
else:
|
|
34
|
+
# Use platformdirs default for Windows
|
|
35
|
+
return platformdirs.user_data_dir(APP_NAME, APP_AUTHOR)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_app_path():
|
|
39
|
+
"""
|
|
40
|
+
Returns the base directory of the application, whether running from source or as an executable.
|
|
41
|
+
"""
|
|
42
|
+
if getattr(sys, "frozen", False):
|
|
43
|
+
# Running in a bundle (PyInstaller)
|
|
44
|
+
return os.path.dirname(sys.executable)
|
|
45
|
+
else:
|
|
46
|
+
# Running in a normal Python environment
|
|
47
|
+
return os.path.dirname(os.path.abspath(__file__))
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_config_paths(args=None):
|
|
51
|
+
"""
|
|
52
|
+
Returns a list of possible config file paths in order of priority:
|
|
53
|
+
1. Command line argument (if provided)
|
|
54
|
+
2. User config directory (~/.mmrelay/config/ on Linux)
|
|
55
|
+
3. Current directory (for backward compatibility)
|
|
56
|
+
4. Application directory (for backward compatibility)
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
args: The parsed command-line arguments
|
|
60
|
+
"""
|
|
61
|
+
paths = []
|
|
62
|
+
|
|
63
|
+
# Check command line arguments for config path
|
|
64
|
+
if args and args.config:
|
|
65
|
+
paths.append(os.path.abspath(args.config))
|
|
66
|
+
|
|
67
|
+
# Check user config directory (preferred location)
|
|
68
|
+
if sys.platform in ["linux", "darwin"]:
|
|
69
|
+
# Use ~/.mmrelay/ for Linux and Mac
|
|
70
|
+
user_config_dir = get_base_dir()
|
|
71
|
+
else:
|
|
72
|
+
# Use platformdirs default for Windows
|
|
73
|
+
user_config_dir = platformdirs.user_config_dir(APP_NAME, APP_AUTHOR)
|
|
74
|
+
|
|
75
|
+
os.makedirs(user_config_dir, exist_ok=True)
|
|
76
|
+
user_config_path = os.path.join(user_config_dir, "config.yaml")
|
|
77
|
+
paths.append(user_config_path)
|
|
78
|
+
|
|
79
|
+
# Check current directory (for backward compatibility)
|
|
80
|
+
current_dir_config = os.path.join(os.getcwd(), "config.yaml")
|
|
81
|
+
paths.append(current_dir_config)
|
|
82
|
+
|
|
83
|
+
# Check application directory (for backward compatibility)
|
|
84
|
+
app_dir_config = os.path.join(get_app_path(), "config.yaml")
|
|
85
|
+
paths.append(app_dir_config)
|
|
86
|
+
|
|
87
|
+
return paths
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_data_dir():
|
|
91
|
+
"""
|
|
92
|
+
Returns the directory for storing application data files.
|
|
93
|
+
Creates the directory if it doesn't exist.
|
|
94
|
+
"""
|
|
95
|
+
if sys.platform in ["linux", "darwin"]:
|
|
96
|
+
# Use ~/.mmrelay/data/ for Linux and Mac
|
|
97
|
+
data_dir = os.path.join(get_base_dir(), "data")
|
|
98
|
+
else:
|
|
99
|
+
# Use platformdirs default for Windows
|
|
100
|
+
data_dir = platformdirs.user_data_dir(APP_NAME, APP_AUTHOR)
|
|
101
|
+
|
|
102
|
+
os.makedirs(data_dir, exist_ok=True)
|
|
103
|
+
return data_dir
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def get_log_dir():
|
|
107
|
+
"""
|
|
108
|
+
Returns the directory for storing log files.
|
|
109
|
+
Creates the directory if it doesn't exist.
|
|
110
|
+
"""
|
|
111
|
+
if sys.platform in ["linux", "darwin"]:
|
|
112
|
+
# Use ~/.mmrelay/logs/ for Linux and Mac
|
|
113
|
+
log_dir = os.path.join(get_base_dir(), "logs")
|
|
114
|
+
else:
|
|
115
|
+
# Use platformdirs default for Windows
|
|
116
|
+
log_dir = platformdirs.user_log_dir(APP_NAME, APP_AUTHOR)
|
|
117
|
+
|
|
118
|
+
os.makedirs(log_dir, exist_ok=True)
|
|
119
|
+
return log_dir
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# Set up a basic logger for config
|
|
123
|
+
logger = logging.getLogger("Config")
|
|
124
|
+
logger.setLevel(logging.INFO)
|
|
125
|
+
handler = logging.StreamHandler()
|
|
126
|
+
handler.setFormatter(
|
|
127
|
+
logging.Formatter(
|
|
128
|
+
fmt="%(asctime)s %(levelname)s:%(name)s:%(message)s",
|
|
129
|
+
datefmt="%Y-%m-%d %H:%M:%S %z",
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
logger.addHandler(handler)
|
|
133
|
+
|
|
134
|
+
# Initialize empty config
|
|
135
|
+
relay_config = {}
|
|
136
|
+
config_path = None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def set_config(module, passed_config):
|
|
140
|
+
"""
|
|
141
|
+
Set the configuration for a module.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
module: The module to set the configuration for
|
|
145
|
+
passed_config: The configuration dictionary to use
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The updated config
|
|
149
|
+
"""
|
|
150
|
+
# Set the module's config variable
|
|
151
|
+
module.config = passed_config
|
|
152
|
+
|
|
153
|
+
# Handle module-specific setup based on module name
|
|
154
|
+
module_name = module.__name__.split(".")[-1]
|
|
155
|
+
|
|
156
|
+
if module_name == "matrix_utils":
|
|
157
|
+
# Set Matrix-specific configuration
|
|
158
|
+
if hasattr(module, "matrix_homeserver") and "matrix" in passed_config:
|
|
159
|
+
module.matrix_homeserver = passed_config["matrix"]["homeserver"]
|
|
160
|
+
module.matrix_rooms = passed_config["matrix_rooms"]
|
|
161
|
+
module.matrix_access_token = passed_config["matrix"]["access_token"]
|
|
162
|
+
module.bot_user_id = passed_config["matrix"]["bot_user_id"]
|
|
163
|
+
|
|
164
|
+
elif module_name == "meshtastic_utils":
|
|
165
|
+
# Set Meshtastic-specific configuration
|
|
166
|
+
if hasattr(module, "matrix_rooms") and "matrix_rooms" in passed_config:
|
|
167
|
+
module.matrix_rooms = passed_config["matrix_rooms"]
|
|
168
|
+
|
|
169
|
+
# If the module still has a setup_config function, call it for backward compatibility
|
|
170
|
+
if hasattr(module, "setup_config") and callable(module.setup_config):
|
|
171
|
+
module.setup_config()
|
|
172
|
+
|
|
173
|
+
return passed_config
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def load_config(config_file=None, args=None):
|
|
177
|
+
"""Load the configuration from the specified file or search for it.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
config_file (str, optional): Path to the config file. If None, search for it.
|
|
181
|
+
args: The parsed command-line arguments
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
dict: The loaded configuration
|
|
185
|
+
"""
|
|
186
|
+
global relay_config, config_path
|
|
187
|
+
|
|
188
|
+
# If a specific config file was provided, use it
|
|
189
|
+
if config_file and os.path.isfile(config_file):
|
|
190
|
+
logger.info(f"Loading configuration from: {config_file}")
|
|
191
|
+
with open(config_file, "r") as f:
|
|
192
|
+
relay_config = yaml.load(f, Loader=SafeLoader)
|
|
193
|
+
config_path = config_file
|
|
194
|
+
return relay_config
|
|
195
|
+
|
|
196
|
+
# Otherwise, search for a config file
|
|
197
|
+
config_paths = get_config_paths(args)
|
|
198
|
+
|
|
199
|
+
# Try each config path in order until we find one that exists
|
|
200
|
+
for path in config_paths:
|
|
201
|
+
if os.path.isfile(path):
|
|
202
|
+
config_path = path
|
|
203
|
+
logger.info(f"Loading configuration from: {config_path}")
|
|
204
|
+
with open(config_path, "r") as f:
|
|
205
|
+
relay_config = yaml.load(f, Loader=SafeLoader)
|
|
206
|
+
logger.info(f"Loaded configuration with keys: {list(relay_config.keys())}")
|
|
207
|
+
return relay_config
|
|
208
|
+
|
|
209
|
+
# No config file found
|
|
210
|
+
logger.error("Configuration file not found in any of the following locations:")
|
|
211
|
+
for path in config_paths:
|
|
212
|
+
logger.error(f" - {path}")
|
|
213
|
+
logger.error("Using empty configuration. This will likely cause errors.")
|
|
214
|
+
logger.error(
|
|
215
|
+
"Run 'mmrelay --generate-config' to generate a sample configuration file."
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
return relay_config
|