diff --git a/docs/docs/documentation/getting-started/installation/backend-config.md b/docs/docs/documentation/getting-started/installation/backend-config.md index baec1fa44d26..d913a23dbab1 100644 --- a/docs/docs/documentation/getting-started/installation/backend-config.md +++ b/docs/docs/documentation/getting-started/installation/backend-config.md @@ -16,7 +16,8 @@ | TZ | UTC | Must be set to get correct date/time on the server | | ALLOW_SIGNUP\* | false | Allow user sign-up without token | | LOG_CONFIG_OVERRIDE | | Override the config for logging with a custom path | -| LOG_LEVEL | info | logging level configured | +| LOG_LEVEL | info | Logging level configured | +| DAILY_SCHEDULE_TIME | 23:45 | The time of day to run the daily tasks. | \* Starting in v1.4.0 this was changed to default to `false` as apart of a security review of the application. diff --git a/mealie/core/settings/settings.py b/mealie/core/settings/settings.py index 8d6c6afea2c7..f966729bced6 100644 --- a/mealie/core/settings/settings.py +++ b/mealie/core/settings/settings.py @@ -57,6 +57,8 @@ class AppSettings(BaseSettings): ALLOW_SIGNUP: bool = False + DAILY_SCHEDULE_TIME: str = "23:45" + # =============================================== # Security Configuration @@ -199,7 +201,11 @@ class AppSettings(BaseSettings): def OIDC_READY(self) -> bool: """Validates OIDC settings are all set""" - required = {self.OIDC_CLIENT_ID, self.OIDC_CONFIGURATION_URL, self.OIDC_USER_CLAIM} + required = { + self.OIDC_CLIENT_ID, + self.OIDC_CONFIGURATION_URL, + self.OIDC_USER_CLAIM, + } not_none = None not in required valid_group_claim = True if (not self.OIDC_USER_GROUP or not self.OIDC_ADMIN_GROUP) and not self.OIDC_GROUPS_CLAIM: diff --git a/mealie/services/scheduler/scheduler_service.py b/mealie/services/scheduler/scheduler_service.py index 8a930bc0637c..0f121ccf1338 100644 --- a/mealie/services/scheduler/scheduler_service.py +++ b/mealie/services/scheduler/scheduler_service.py @@ -1,6 +1,9 @@ +import asyncio +from datetime import datetime, timedelta from pathlib import Path from mealie.core import root_logger +from mealie.core.config import get_app_settings from mealie.services.scheduler.runner import repeat_every from .scheduler_registry import SchedulerRegistry @@ -18,18 +21,53 @@ class SchedulerService: @staticmethod async def start(): await run_minutely() - await run_daily() await run_hourly() + # Wait to trigger our daily run until our given "daily time", so having asyncio handle it. + asyncio.create_task(schedule_daily()) + + +async def schedule_daily(): + now = datetime.now() + daily_schedule_time = get_app_settings().DAILY_SCHEDULE_TIME + logger.debug( + "Current time is %s and DAILY_SCHEDULE_TIME is %s", + str(now), + daily_schedule_time, + ) + try: + hour_target, minute_target = _parse_daily_schedule_time(daily_schedule_time) + except Exception: + logger.exception(f"Unable to parse {daily_schedule_time=}") + hour_target = 23 + minute_target = 45 + + hours_until = ((hour_target - now.hour) % 24) or 24 + minutes_until = (minute_target - now.minute) % 60 + logger.debug("Hours until %s and minutes until %s", str(hours_until), str(minutes_until)) + + delta = timedelta(hours=hours_until, minutes=minutes_until) + target_time = (now + delta).replace(microsecond=0, second=0) + logger.info("Daily tasks scheduled for %s", str(target_time)) + wait_seconds = (target_time - now).total_seconds() + await asyncio.sleep(wait_seconds) + await run_daily() + + +def _parse_daily_schedule_time(time): + hour_target = int(time.split(":")[0]) + minute_target = int(time.split(":")[1]) + return hour_target, minute_target + def _scheduled_task_wrapper(callable): try: callable() except Exception as e: - logger.error(f"Error in scheduled task func='{callable.__name__}': exception='{e}'") + logger.error("Error in scheduled task func='%s': exception='%s'", callable.__name__, e) -@repeat_every(minutes=MINUTES_DAY, wait_first=True, logger=logger) +@repeat_every(minutes=MINUTES_DAY, wait_first=False, logger=logger) def run_daily(): logger.debug("Running daily callbacks") for func in SchedulerRegistry._daily: