diff --git a/bot/vikingbot/__init__.py b/bot/vikingbot/__init__.py index ce093e3dd..a9a34240e 100644 --- a/bot/vikingbot/__init__.py +++ b/bot/vikingbot/__init__.py @@ -3,8 +3,13 @@ """ import warnings +from importlib.metadata import PackageNotFoundError, version as _pkg_version + +try: + __version__ = _pkg_version("openviking") +except PackageNotFoundError: + __version__ = "0.0.0+unknown" -__version__ = "0.1.3" __logo__ = "🐈" # Suppress RequestsDependencyWarning from requests module diff --git a/bot/vikingbot/cli/commands.py b/bot/vikingbot/cli/commands.py index 907f3263e..431e62361 100644 --- a/bot/vikingbot/cli/commands.py +++ b/bot/vikingbot/cli/commands.py @@ -4,6 +4,7 @@ import json import os import select +import socket import sys import time import warnings @@ -83,6 +84,32 @@ def _init_bot_data(config): set_bot_data_path(config.bot_data_path) +def _abort_if_port_in_use(port: int, label: str) -> None: + """Exit with a clear message if anything is already listening on ``port``. + + Without this check, a stale process holding the port keeps serving + traffic while a freshly-started gateway silently fails to bind — the + operator believes they upgraded but the old (potentially unpatched) + binary is still answering requests. + """ + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(0.5) + try: + s.connect(("127.0.0.1", port)) + in_use = True + except (ConnectionRefusedError, socket.timeout, OSError): + in_use = False + if in_use: + print( + f"Error: {label} port {port} is already in use.\n" + f" A previous process is still bound — refusing to start a duplicate.\n" + f" Identify it: lsof -nP -iTCP:{port} -sTCP:LISTEN\n" + f" Kill it, then retry.", + file=sys.stderr, + ) + sys.exit(1) + + # --------------------------------------------------------------------------- # CLI input: prompt_toolkit for editing, paste, history, and display # --------------------------------------------------------------------------- @@ -249,6 +276,8 @@ def gateway( ): """Start the vikingbot gateway with OpenAPI chat enabled by default.""" + _abort_if_port_in_use(port, "vikingbot gateway") + if verbose: import logging diff --git a/openviking/server/bootstrap.py b/openviking/server/bootstrap.py index c773fc630..0eda1ee61 100644 --- a/openviking/server/bootstrap.py +++ b/openviking/server/bootstrap.py @@ -5,6 +5,7 @@ import argparse import os import shutil +import socket import subprocess import sys import time @@ -34,6 +35,34 @@ def _get_version() -> str: return "unknown" +VIKINGBOT_DEFAULT_PORT = 18790 + + +def _abort_if_port_in_use(port: int, label: str) -> None: + """Exit with a clear message if anything is already listening on ``port``. + + Without this, ``--with-bot`` would spawn a vikingbot subprocess that + silently fails to bind while a stale process keeps serving traffic — + the operator believes they upgraded but the old binary still answers. + """ + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.settimeout(0.5) + try: + s.connect(("127.0.0.1", port)) + in_use = True + except (ConnectionRefusedError, socket.timeout, OSError): + in_use = False + if in_use: + print( + f"Error: {label} port {port} is already in use.\n" + f" A previous process is still bound — refusing to start a duplicate.\n" + f" Identify it: lsof -nP -iTCP:{port} -sTCP:LISTEN\n" + f" Kill it, then retry.", + file=sys.stderr, + ) + sys.exit(1) + + def _normalize_host_arg(host: Optional[str]) -> Optional[str]: """Normalize special CLI host values.""" if host is None: @@ -170,6 +199,7 @@ def main(): bot_process: Optional[BotProcess] = None if config.with_bot: + _abort_if_port_in_use(VIKINGBOT_DEFAULT_PORT, "vikingbot gateway") print(f"Bot API proxy enabled, forwarding to {config.bot_api_url}") # Determine if bot logging should be enabled enable_bot_logging = args.enable_bot_logging