Index: cloud_sync.py =================================================================== diff -u -rd17748c773b5a38a9ef0e5ffa59406f5c2e023df -r99d35f3d5898d50372e1745b96b393db40cd8394 --- cloud_sync.py (.../cloud_sync.py) (revision d17748c773b5a38a9ef0e5ffa59406f5c2e023df) +++ cloud_sync.py (.../cloud_sync.py) (revision 99d35f3d5898d50372e1745b96b393db40cd8394) @@ -15,11 +15,16 @@ from cloudsync.utils.globals import * from cloudsync.utils.helpers import * from cloudsync.utils.logging import LoggingConfig +from cloudsync.utils.watchdog import Watchdog, make_restart_fn + +import hmac import os +import signal import sys +import threading -VERSION = "0.5.0" +VERSION = "0.5.2" arguments = sys.argv @@ -30,11 +35,22 @@ logconf = LoggingConfig() logconf.initiate(app=app) +# API key authentication for deployed instances +CS_API_KEY = os.environ.get('CS_API_KEY') + +@app.before_request +def check_api_key(): + if CS_API_KEY is None: + return # no key configured — allow all requests + key = request.headers.get('X-Api-Key', '') + if not hmac.compare_digest(key, CS_API_KEY): + return {"message": "Unauthorized: invalid or missing API key"}, 401 + sleep(5) # wait for UI to prepare the configurations partition try: ok = False - print(SETUP_CONSOLE_LINE) + g_utils.logger.info(SETUP_CONSOLE_LINE) if EXEC_MODE_UPGRADE_KEY in arguments: # ---------- upgrade # Read from $HOME @@ -45,15 +61,15 @@ else: if EXEC_MODE_UPDATE_KEY in arguments: # ---------- update EXEC_MODE = EXEC_MODE_UPDATE - print(f"CloudSync starting in {EXEC_MODE} mode") - print("CloudSync update config started...") + g_utils.logger.info("CloudSync starting in %s mode", EXEC_MODE) + g_utils.logger.info("CloudSync update config started...") # Update the $HOME from /var/configuraitons/ and update result in both for later update oldConfig = helpers_read_config(OPERATION_CONFIG_FILE_PATH) newConfig = helpers_read_config(CONFIG_PATH) newConfig.update(oldConfig) helpers_write_config(None , CONFIG_PATH , newConfig) helpers_write_config(OPERATION_CONFIG_PATH, OPERATION_CONFIG_FILE_PATH, newConfig) - print("CloudSync update config done.") + g_utils.logger.info("CloudSync update config done.") # Read from /var/configuraitons/ # ---------- normal if os.path.isfile(OPERATION_CONFIG_FILE_PATH) and os.access(OPERATION_CONFIG_FILE_PATH, os.R_OK): #TODO test if this check needed? @@ -62,16 +78,16 @@ ok = True if ok: - print(f"CloudSync started in {EXEC_MODE} mode") - print(f"Using config: {CONFIG_PATH}") + g_utils.logger.info("CloudSync started in %s mode", EXEC_MODE) + g_utils.logger.info("Using config: %s", CONFIG_PATH) else: g_utils.logger.error(f"Error reading config file in {EXEC_MODE}") - print(SETUP_CONSOLE_LINE) + g_utils.logger.info(SETUP_CONSOLE_LINE) except Exception as e: g_utils.logger.error(f"Error reading config file - {e}") - print(SETUP_CONSOLE_LINE) + g_utils.logger.info(SETUP_CONSOLE_LINE) sys.exit(0) try: @@ -116,11 +132,52 @@ g_utils.logger.debug("Current config path: {0}".format(CONFIG_PATH)) parser = reqparse.RequestParser() + + # Watchdog — monitors critical daemon threads (operation mode only). + # During registration the device is semi-managed by the manufacturing + # tool, so autonomous recovery is unnecessary and could interfere with + # the registration-to-operation transition. + watchdog = Watchdog(logger=app.logger) + if g_config[CONFIG_DEVICE][CONFIG_DEVICE_MODE] == 'operation': + watchdog.register("reachability", lambda: reachability_provider.thread, + make_restart_fn(reachability_provider, "thread", reachability_provider.reachability_test)) + watchdog.register("output_bus", lambda: output_channel.thread, + make_restart_fn(output_channel, "thread", output_channel.scheduler)) + watchdog.register("error_handler", lambda: error_handler.thread, + make_restart_fn(error_handler, "thread", error_handler.scheduler)) + watchdog.register("network_request_handler", lambda: network_request_handler.thread, + make_restart_fn(network_request_handler, "thread", network_request_handler.scheduler)) + watchdog.register("message_handler", lambda: message_handler.thread, + make_restart_fn(message_handler, "thread", message_handler.scheduler)) + watchdog.register("file_input_bus", lambda: ui_cs_bus.thread, + make_restart_fn(ui_cs_bus, "thread", ui_cs_bus.input_channel_handler)) + watchdog.register("heartbeat", lambda: heartbeat_provider.thread, + make_restart_fn(heartbeat_provider, "thread", heartbeat_provider.heartbeat)) + watchdog.start() + g_utils.logger.info("Watchdog started (operation mode)") + else: + g_utils.logger.info("Watchdog skipped (registration mode — device is managed)") + except Exception as e: g_utils.logger.error("Failed to start CS - {0}".format(e)) sys.exit(0) +# Signal handlers for graceful shutdown +shutdown_event = threading.Event() +def _graceful_shutdown(signum, _frame): + g_utils.logger.info("Received signal %d, initiating graceful shutdown...", signum) + shutdown_event.set() + try: + watchdog.stop() + except Exception: + pass + sys.exit(0) + +signal.signal(signal.SIGTERM, _graceful_shutdown) +signal.signal(signal.SIGINT, _graceful_shutdown) + + @api.route("/version") class Version(Resource): @@ -245,10 +302,10 @@ # END - REGISTRATION ENDPOINTS - def main(): if g_config[CONFIG_DEVICE][CONFIG_DEVICE_MODE] == 'registration': - app.run(debug=False, use_reloader=False, host="0.0.0.0",port=g_config[CONFIG_DEVICE][CONFIG_DEVICE_PORT]) + app.run(debug=False, use_reloader=False, host="0.0.0.0", + port=g_config[CONFIG_DEVICE][CONFIG_DEVICE_PORT]) else: while True: sleep(1)