diff --git a/moonraker/moonraker.py b/moonraker/moonraker.py index f05d7fe..049aaa6 100755 --- a/moonraker/moonraker.py +++ b/moonraker/moonraker.py @@ -122,9 +122,12 @@ class Server: self.register_upload_handler = app.register_upload_handler self.register_api_transport = app.register_api_transport - log_warn: Optional[str] = args.get('log_warning') - if log_warn is not None: + log_warn = args.get('log_warning', "") + if log_warn: self.add_warning(log_warn) + cfg_warn = args.get("config_warning", "") + if cfg_warn: + self.add_warning(cfg_warn) self.register_endpoint( "/server/info", ['GET'], self._handle_info_request) @@ -188,6 +191,10 @@ class Server: if optional_comps: await asyncio.gather(*optional_comps) + if not self.warnings: + cfg_file = self.app_args['config_file'] + await self.event_loop.run_in_thread(utils.backup_config, cfg_file) + # Start HTTP Server logging.info( f"Starting Moonraker on ({self.host}, {self.port}), " @@ -859,7 +866,8 @@ def main() -> None: os.path.expanduser(cmd_line_args.logfile)) app_args['software_version'] = version ql, file_logger, warning = utils.setup_logging(app_args) - app_args['log_warning'] = warning + if warning is not None: + app_args['log_warning'] = warning if sys.version_info < (3, 7): msg = f"Moonraker requires Python 3.7 or above. " \ @@ -871,10 +879,27 @@ def main() -> None: # Start asyncio event loop and server event_loop = EventLoop() + alt_config_loaded = False estatus = 0 while True: try: server = Server(app_args, file_logger, event_loop) + except confighelper.ConfigError as e: + backup_cfg = utils.find_config_backup(app_args['config_file']) + if alt_config_loaded or backup_cfg is None: + logging.exception("Server Config Error") + estatus = 1 + break + app_args['config_file'] = backup_cfg + app_args['config_warning'] = ( + f"Server configuration error: {e}\n" + f"Loaded server from most recent working configuration:" + f" '{app_args['config_file']}'\n" + f"Please fix the issue in moonraker.conf and restart " + f"the server." + ) + alt_config_loaded = True + continue except Exception: logging.exception("Moonraker Error") estatus = 1 @@ -887,6 +912,12 @@ def main() -> None: break if server.exit_reason == "terminate": break + # Restore the original config and clear the warning + # before the server restarts + if alt_config_loaded: + app_args['config_file'] = cmd_line_args.configfile + app_args.pop('config_warning', None) + alt_config_loaded = False event_loop.close() # Since we are running outside of the the server # it is ok to use a blocking sleep here diff --git a/moonraker/utils.py b/moonraker/utils.py index 3e72ea5..5a003d6 100644 --- a/moonraker/utils.py +++ b/moonraker/utils.py @@ -18,6 +18,8 @@ import hashlib import json import shlex import re +import shutil +import filecmp from queue import SimpleQueue as Queue # Annotation imports @@ -230,3 +232,22 @@ def load_system_module(name: str) -> ModuleType: else: raise ServerError(f"Unable to import module {name}") return module + +def backup_config(cfg_path: str) -> None: + cfg = pathlib.Path(cfg_path).expanduser().resolve() + backup = cfg.parent.joinpath(f".{cfg.name}.bkp") + try: + if backup.exists() and filecmp.cmp(cfg, backup): + # Backup already exists and is current + return + shutil.copy2(cfg, backup) + logging.info(f"Backing up last working configuration to '{backup}'") + except Exception: + logging.exception("Failed to create a backup") + +def find_config_backup(cfg_path: str) -> Optional[str]: + cfg = pathlib.Path(cfg_path).expanduser().resolve() + backup = cfg.parent.joinpath(f".{cfg.name}.bkp") + if backup.is_file(): + return str(backup) + return None