"""CLI entry point for the I2P router.""" import argparse import asyncio import logging import os import signal import sys from i2p_router.bootstrap import RouterBootstrap from i2p_router.config import RouterConfig def main() -> None: parser = argparse.ArgumentParser(description="I2P Python Router") parser.add_argument("--host", default="0.0.0.0", help="Listen host (default: 0.0.0.0)") parser.add_argument("--port", type=int, default=9700, help="NTCP2 listen port (default: 9700)") parser.add_argument("--health-port", type=int, default=9701, help="Health endpoint port (default: 9701)") parser.add_argument("--data-dir", default="~/.i2p-python", help="Data directory (default: ~/.i2p-python)") parser.add_argument("--reseed", action="store_true", help="Force reseed on start") parser.add_argument( "--setup", nargs="?", const="auto", metavar="MODE", help="Run first-run setup wizard (modes: auto, paranoid, performance, interactive)", ) parser.add_argument( "--log-level", default="INFO", choices=["DEBUG", "INFO", "WARNING", "ERROR"], help="Log level (default: INFO)", ) args = parser.parse_args() # Console logging log_level = getattr(logging, args.log_level) log_format = "%(asctime)s %(levelname)-8s %(name)s: %(message)s" root_logger = logging.getLogger() root_logger.setLevel(log_level) console_handler = logging.StreamHandler(sys.stdout) console_handler.setFormatter(logging.Formatter(log_format)) root_logger.addHandler(console_handler) # File logging (always DEBUG for post-mortem analysis) data_dir = os.path.expanduser(args.data_dir) os.makedirs(data_dir, exist_ok=True) log_path = os.path.join(data_dir, "router.log") file_handler = logging.FileHandler(log_path) file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(logging.Formatter(log_format)) root_logger.addHandler(file_handler) log = logging.getLogger(__name__) # Run setup wizard if --setup or first run from i2p_apps.setup.wizard import SetupWizard wizard = SetupWizard(data_dir=data_dir) if args.setup: mode = args.setup if mode == "interactive": wizard.run_interactive() else: wizard.run_auto(mode=mode) log.info("Setup complete, config saved to %s", data_dir) elif wizard.is_first_run(): log.info("First run detected — running auto setup") wizard.run_auto(mode="normal") log.info("I2P Python Router starting") log.info(" NTCP2 port: %d", args.port) log.info(" Health port: %d", args.health_port) log.info(" Data dir: %s", args.data_dir) log.info(" Log file: %s", log_path) log.info(" Log level: %s (file always DEBUG)", args.log_level) config = RouterConfig( listen_host=args.host, listen_port=args.port, health_port=args.health_port, data_dir=args.data_dir, ) config = RouterConfig.with_env_overrides(config) bootstrap = RouterBootstrap(config) async def run() -> None: await bootstrap.start() log.info( "Router running — NTCP2 on %s:%d, health on %s:%d", args.host, args.port, args.host, args.health_port, ) stop = asyncio.Event() loop = asyncio.get_event_loop() for sig in (signal.SIGTERM, signal.SIGINT): loop.add_signal_handler(sig, stop.set) await stop.wait() log.info("Signal received, shutting down...") await bootstrap.shutdown() asyncio.run(run()) if __name__ == "__main__": main()