e7c1a04 · Reload the map on SIGHUP · 5 days ago

python84 lines · 2.5 KB
1"""Axiom scoped relay.
2
3Reads a channel-mapping markdown file (path or https URL), then:
4 1. Listens on management channels (direction: in)
5 2. Fans broadcasts into scoped rooms
6 3. Collects replies and posts a report
7
8Scope is a wall. Channels not listed in the mapping are invisible.
9SIGHUP reloads the map without a restart.
10"""
11
12from __future__ import annotations
13
14import argparse
15import logging
16import signal
17import sys
18from pathlib import Path
19
20if __package__ is None:
21 sys.path.insert(0, str(Path(__file__).resolve().parent))
22
23from discord_adapter import DiscordAdapter
24from mapping import load_mapping_from
25from report import format_report
26from scope import ScopeGuard
27from slack_adapter import SlackAdapter
28
29ADAPTERS = {
30 "slack": SlackAdapter,
31 "discord": DiscordAdapter,
32}
33
34
35class Runtime:
36 def __init__(self, source: str, platform: str | None) -> None:
37 self.source = source
38 self.platform_override = platform
39 self.reload()
40
41 def reload(self) -> None:
42 mapping = load_mapping_from(self.source)
43 platform = self.platform_override or mapping.platform
44 if platform not in ADAPTERS:
45 raise SystemExit(f"unknown platform: {platform}")
46 self.mapping = mapping
47 self.platform = platform
48 self.guard = ScopeGuard(mapping)
49 self.adapter = ADAPTERS[platform](mapping, self.guard)
50 logging.info("loaded scope=%s ids=%d", mapping.scope, len(mapping.ids()))
51
52
53def main(argv: list[str] | None = None) -> int:
54 parser = argparse.ArgumentParser(prog="axiom-relay")
55 parser.add_argument("--mapping", required=True, help="path or https URL")
56 parser.add_argument("--platform", choices=sorted(ADAPTERS))
57 parser.add_argument("--dry-run", action="store_true")
58 parser.add_argument("--collect-seconds", type=int, default=None)
59 args = parser.parse_args(argv)
60
61 runtime = Runtime(args.mapping, args.platform)
62 collect = args.collect_seconds or runtime.mapping.collect_seconds
63
64 if args.dry_run:
65 runtime.adapter.describe(sys.stdout)
66 sys.stdout.write(format_report(
67 mapping=runtime.mapping,
68 guard=runtime.guard,
69 broadcast_id=runtime.mapping.by_role("management")[0].id,
70 reply_count=0,
71 collect_seconds=collect,
72 ))
73 return 0
74
75 if hasattr(signal, "SIGHUP"):
76 signal.signal(signal.SIGHUP, lambda *_: runtime.reload())
77
78 runtime.adapter.run(collect_seconds=collect)
79 return 0
80
81
82if __name__ == "__main__":
83 raise SystemExit(main())
84