feat: per-network proxy override, CERT ADD timing fix
config: add optional proxy_host/proxy_port to NetworkConfig router: resolve per-network proxy via _proxy_for() helper commands: trigger REHASH reconnect on proxy config changes network: send CERT ADD before CAP END to beat K-line race Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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}")
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user