2f40f5e508
Per-network, per-nick client certificates (EC P-256, self-signed, 10-year validity) stored as combined PEM files. Authentication cascade: SASL EXTERNAL > SASL PLAIN > NickServ IDENTIFY. New commands: GENCERT, CERTFP, DELCERT. GENCERT auto-registers the fingerprint with NickServ CERT ADD when the network is connected. Includes email verification module for NickServ registration and expanded NickServ interaction (IDENTIFY, REGISTER, VERIFY).
104 lines
2.7 KiB
Python
104 lines
2.7 KiB
Python
"""Entry point for bouncer."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import logging
|
|
import signal
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
from bouncer import commands
|
|
from bouncer.backlog import Backlog
|
|
from bouncer.cli import parse_args
|
|
from bouncer.config import load
|
|
from bouncer.router import Router
|
|
from bouncer.server import start
|
|
|
|
log = logging.getLogger("bouncer")
|
|
|
|
|
|
def _setup_logging(verbose: bool) -> None:
|
|
level = logging.DEBUG if verbose else logging.INFO
|
|
fmt = "\033[2m%(asctime)s\033[0m %(levelname)-5s \033[38;5;110m%(name)s\033[0m %(message)s"
|
|
datefmt = "%H:%M:%S"
|
|
logging.basicConfig(level=level, format=fmt, datefmt=datefmt, force=True)
|
|
# Also log to file for container environments
|
|
fh = logging.FileHandler("/data/bouncer.log")
|
|
fh.setLevel(level)
|
|
fh.setFormatter(logging.Formatter("%(asctime)s %(levelname)-5s %(name)s %(message)s", datefmt))
|
|
logging.getLogger().addHandler(fh)
|
|
|
|
|
|
async def _run(config_path: Path, verbose: bool) -> None:
|
|
_setup_logging(verbose)
|
|
|
|
cfg = load(config_path)
|
|
log.info("loaded config: %d network(s)", len(cfg.networks))
|
|
|
|
# Data directory alongside config
|
|
data_dir = config_path.parent
|
|
db_path = data_dir / "bouncer.db"
|
|
|
|
backlog = Backlog(db_path)
|
|
await backlog.open()
|
|
|
|
commands.STARTUP_TIME = time.time()
|
|
commands.CONFIG_PATH = config_path
|
|
commands.DATA_DIR = data_dir
|
|
|
|
router = Router(cfg, backlog, data_dir=data_dir)
|
|
await router.start_networks()
|
|
|
|
server = await start(cfg.bouncer, router)
|
|
|
|
# Graceful shutdown on SIGINT/SIGTERM
|
|
loop = asyncio.get_running_loop()
|
|
stop_event = asyncio.Event()
|
|
|
|
def _signal_handler() -> None:
|
|
log.info("shutting down...")
|
|
stop_event.set()
|
|
|
|
for sig in (signal.SIGINT, signal.SIGTERM):
|
|
loop.add_signal_handler(sig, _signal_handler)
|
|
|
|
await stop_event.wait()
|
|
|
|
server.close()
|
|
await server.wait_closed()
|
|
await router.stop_networks()
|
|
await backlog.close()
|
|
log.info("shutdown complete")
|
|
|
|
|
|
def main() -> None:
|
|
"""CLI entry point."""
|
|
args = parse_args()
|
|
|
|
if not args.config.exists():
|
|
print(f"error: config not found: {args.config}", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
try:
|
|
if args.cprofile:
|
|
import cProfile
|
|
|
|
prof = cProfile.Profile()
|
|
prof.enable()
|
|
try:
|
|
asyncio.run(_run(args.config, args.verbose))
|
|
finally:
|
|
prof.disable()
|
|
prof.dump_stats(str(args.cprofile))
|
|
print(f"cProfile stats written to {args.cprofile}", file=sys.stderr)
|
|
else:
|
|
asyncio.run(_run(args.config, args.verbose))
|
|
except KeyboardInterrupt:
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|