From d2c92c2deb4fd2296d17bd502a428fd3ae912c9d Mon Sep 17 00:00:00 2001 From: viewit Date: Tue, 7 Jul 2026 01:23:01 +0200 Subject: [PATCH] fix(settings): stale env vars survived bridge restart, reverting saved changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _restart_bridge() cleared a hardcoded, manually-maintained list of env keys before restarting — every setting added since (vibration_compensation, host_ip, poll_interval, verbose_http_log) was missing from it. The old process's env var therefore survived into the new process and silently overrode the freshly written config.ini value, making toggles appear to revert right after saving. Fixed at the root: config_loader.CONFIG_ENV_MAPPING is now the single source of truth for which env keys back which config.ini options, and _restart_bridge() derives its cleanup list from it. A newly added setting can't be forgotten here again. --- config_loader.py | 49 ++++++++++++++++++++++---------------- kobrax_moonraker_bridge.py | 15 +++++++----- 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/config_loader.py b/config_loader.py index ed94fa7..459942e 100644 --- a/config_loader.py +++ b/config_loader.py @@ -47,32 +47,39 @@ def _load_env_file(path: pathlib.Path): os.environ[key] = val +# Single source of truth for env-var <-> config.ini mapping. _restart_bridge() +# in kobrax_moonraker_bridge.py clears exactly these keys from os.environ +# before restarting, so a value removed here or in the UI settings save can +# never survive as a stale env var read by the new process. Add new settings +# here ONLY - no second list to keep in sync. +CONFIG_ENV_MAPPING = { + "PRINTER_IP": (CONFIG_SECTION_CONNECTION, "printer_ip"), + "MQTT_PORT": (CONFIG_SECTION_CONNECTION, "mqtt_port"), + "MQTT_USERNAME": (CONFIG_SECTION_CONNECTION, "username"), + "MQTT_PASSWORD": (CONFIG_SECTION_CONNECTION, "password"), + "MODE_ID": (CONFIG_SECTION_CONNECTION, "mode_id"), + "DEVICE_ID": (CONFIG_SECTION_CONNECTION, "device_id"), + "DEFAULT_AMS_SLOT": (CONFIG_SECTION_PRINT, "default_ams_slot"), + "AUTO_LEVELING": (CONFIG_SECTION_PRINT, "auto_leveling"), + "VIBRATION_COMPENSATION": (CONFIG_SECTION_PRINT, "vibration_compensation"), + "CAMERA_ON_PRINT": (CONFIG_SECTION_PRINT, "camera_on_print"), + "WEB_UPLOAD_WARNING": (CONFIG_SECTION_PRINT, "web_upload_warning"), + "PRINT_START_DIALOG": (CONFIG_SECTION_PRINT, "print_start_dialog"), + "BRIDGE_PRINTER_NAME": (CONFIG_SECTION_BRIDGE, "printer_name"), + "BRIDGE_HOST_IP": (CONFIG_SECTION_BRIDGE, "host_ip"), + "POLL_INTERVAL": (CONFIG_SECTION_BRIDGE, "poll_interval"), + "VERBOSE_HTTP_LOG": (CONFIG_SECTION_BRIDGE, "verbose_http_log"), + "SPOOLMAN_SERVER": (CONFIG_SECTION_SPOOLMAN, "server"), + "SPOOLMAN_SYNC_RATE": (CONFIG_SECTION_SPOOLMAN, "sync_rate"), +} + + def _load_config_file(path: pathlib.Path): """Loads config.ini and sets keys in os.environ (only if not already set).""" cfg = configparser.ConfigParser() cfg.read(path, encoding="utf-8") - mapping = { - "PRINTER_IP": (CONFIG_SECTION_CONNECTION, "printer_ip"), - "MQTT_PORT": (CONFIG_SECTION_CONNECTION, "mqtt_port"), - "MQTT_USERNAME": (CONFIG_SECTION_CONNECTION, "username"), - "MQTT_PASSWORD": (CONFIG_SECTION_CONNECTION, "password"), - "MODE_ID": (CONFIG_SECTION_CONNECTION, "mode_id"), - "DEVICE_ID": (CONFIG_SECTION_CONNECTION, "device_id"), - "DEFAULT_AMS_SLOT": (CONFIG_SECTION_PRINT, "default_ams_slot"), - "AUTO_LEVELING": (CONFIG_SECTION_PRINT, "auto_leveling"), - "VIBRATION_COMPENSATION": (CONFIG_SECTION_PRINT, "vibration_compensation"), - "CAMERA_ON_PRINT": (CONFIG_SECTION_PRINT, "camera_on_print"), - "WEB_UPLOAD_WARNING": (CONFIG_SECTION_PRINT, "web_upload_warning"), - "PRINT_START_DIALOG": (CONFIG_SECTION_PRINT, "print_start_dialog"), - "BRIDGE_PRINTER_NAME": (CONFIG_SECTION_BRIDGE, "printer_name"), - "BRIDGE_HOST_IP": (CONFIG_SECTION_BRIDGE, "host_ip"), - "POLL_INTERVAL": (CONFIG_SECTION_BRIDGE, "poll_interval"), - "VERBOSE_HTTP_LOG": (CONFIG_SECTION_BRIDGE, "verbose_http_log"), - "SPOOLMAN_SERVER": (CONFIG_SECTION_SPOOLMAN, "server"), - "SPOOLMAN_SYNC_RATE": (CONFIG_SECTION_SPOOLMAN, "sync_rate"), - } - for env_key, (section, option) in mapping.items(): + for env_key, (section, option) in CONFIG_ENV_MAPPING.items(): if env_key not in os.environ: try: val = cfg.get(section, option) diff --git a/kobrax_moonraker_bridge.py b/kobrax_moonraker_bridge.py index ccb8624..8d3da7b 100644 --- a/kobrax_moonraker_bridge.py +++ b/kobrax_moonraker_bridge.py @@ -4644,12 +4644,15 @@ class KobraXBridge: log.info("Restarting bridge...") # config_loader caches config.ini values in os.environ ("only if not set"). # On restart, environ must be cleaned, otherwise the new process reads - # the old values instead of the modified config.ini. - for _k in ("PRINTER_IP", "MQTT_PORT", "MQTT_USERNAME", "MQTT_PASSWORD", - "MODE_ID", "DEVICE_ID", "DEFAULT_AMS_SLOT", "AUTO_LEVELING", - "CAMERA_ON_PRINT", "WEB_UPLOAD_WARNING", "PRINT_START_DIALOG", - "FILE_READY_DIALOG", "BRIDGE_PRINTER_NAME", - "SPOOLMAN_SERVER", "SPOOLMAN_SYNC_RATE"): + # the old values instead of the modified config.ini. Keys are derived + # from config_loader.CONFIG_ENV_MAPPING (single source of truth) so a + # newly added setting can never be forgotten here again. + try: + import config_loader as _cl + _restart_env_keys = set(_cl.CONFIG_ENV_MAPPING.keys()) | {"FILE_READY_DIALOG"} + except Exception: + _restart_env_keys = () + for _k in _restart_env_keys: os.environ.pop(_k, None) in_docker = os.path.exists("/.dockerenv") or os.environ.get("KX_IN_DOCKER")