diff --git a/src/bouncer/commands.py b/src/bouncer/commands.py index 751818d..fe58417 100644 --- a/src/bouncer/commands.py +++ b/src/bouncer/commands.py @@ -476,7 +476,9 @@ async def _cmd_rehash(router: Router) -> list[str]: new_net_cfg = new_cfg.networks[name] if (old_net.cfg.host != new_net_cfg.host or old_net.cfg.port != new_net_cfg.port - or old_net.cfg.tls != new_net_cfg.tls): + or old_net.cfg.tls != new_net_cfg.tls + or old_net.cfg.proxy_host != new_net_cfg.proxy_host + or old_net.cfg.proxy_port != new_net_cfg.proxy_port): await router.remove_network(name) await router.add_network(new_net_cfg) lines.append(f" reconnected: {name}") diff --git a/src/bouncer/config.py b/src/bouncer/config.py index 4ffe75c..a256e67 100644 --- a/src/bouncer/config.py +++ b/src/bouncer/config.py @@ -43,8 +43,10 @@ class NetworkConfig: user: str = "" realname: str = "" channels: list[str] = field(default_factory=list) - autojoin: bool = True + autojoin: bool = False password: str | None = None + proxy_host: str | None = None + proxy_port: int | None = None @dataclass(slots=True) @@ -100,6 +102,8 @@ def load(path: Path) -> Config: channels=net_raw.get("channels", []), autojoin=net_raw.get("autojoin", True), password=net_raw.get("password"), + proxy_host=net_raw.get("proxy_host"), + proxy_port=net_raw.get("proxy_port"), ) if not networks: diff --git a/src/bouncer/network.py b/src/bouncer/network.py index 0a83d40..6fde02b 100644 --- a/src/bouncer/network.py +++ b/src/bouncer/network.py @@ -827,11 +827,11 @@ class Network: log.info("[%s] SASL %s authentication successful", self.cfg.name, self._sasl_mechanism) self._status(f"SASL {self._sasl_mechanism} authenticated as {self._sasl_nick}") self._sasl_complete.set() - await self.send_raw("CAP", "END") - # If authenticated via PLAIN but a cert exists, register fingerprint - # immediately (before K-line can disconnect us) + # Register cert fingerprint BEFORE CAP END so NickServ processes + # it while we're still in capability negotiation (before K-line) if self._sasl_mechanism == "PLAIN" and self.data_dir: await self._register_cert_fingerprint() + await self.send_raw("CAP", "END") return if msg.command in ("902", "904", "905"): diff --git a/src/bouncer/router.py b/src/bouncer/router.py index f7065fc..d792337 100644 --- a/src/bouncer/router.py +++ b/src/bouncer/router.py @@ -8,7 +8,7 @@ from pathlib import Path from typing import TYPE_CHECKING from bouncer.backlog import Backlog -from bouncer.config import Config, NetworkConfig +from bouncer.config import Config, NetworkConfig, ProxyConfig from bouncer.irc import IRCMessage from bouncer.namespace import decode_target, encode_message from bouncer.network import Network @@ -91,12 +91,21 @@ class Router: self.clients: list[Client] = [] self._lock = asyncio.Lock() + def _proxy_for(self, net_cfg: NetworkConfig) -> ProxyConfig: + """Return the effective proxy config for a network.""" + if net_cfg.proxy_host is not None: + return ProxyConfig( + host=net_cfg.proxy_host, + port=net_cfg.proxy_port or self.config.proxy.port, + ) + return self.config.proxy + async def start_networks(self) -> None: """Connect to all configured networks.""" for name, net_cfg in self.config.networks.items(): network = Network( cfg=net_cfg, - proxy_cfg=self.config.proxy, + proxy_cfg=self._proxy_for(net_cfg), backlog=self.backlog, on_message=self._on_network_message, on_status=self._on_network_status, @@ -280,7 +289,7 @@ class Router: """Create and start a new network at runtime.""" network = Network( cfg=cfg, - proxy_cfg=self.config.proxy, + proxy_cfg=self._proxy_for(cfg), backlog=self.backlog, on_message=self._on_network_message, on_status=self._on_network_status,