fix: Convert Daily Schedule Time to UTC (#3914)

This commit is contained in:
Michael Genson 2024-07-20 16:57:02 -05:00 committed by GitHub
parent e33b62be2a
commit 5b1e827d45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 66 additions and 33 deletions

View File

@ -4,20 +4,20 @@
### General
| Variables | Default | Description |
| ----------------------------- | :-------------------: | ----------------------------------------------------------------------------------- |
| PUID | 911 | UserID permissions between host OS and container |
| PGID | 911 | GroupID permissions between host OS and container |
| DEFAULT_GROUP | Home | The default group for users |
| BASE_URL | http://localhost:8080 | Used for Notifications |
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
| API_DOCS | True | Turns on/off access to the API documentation locally. |
| TZ | UTC | Must be set to get correct date/time on the server |
| ALLOW_SIGNUP<super>\*</super> | false | Allow user sign-up without token |
| LOG_CONFIG_OVERRIDE | | Override the config for logging with a custom path |
| LOG_LEVEL | info | Logging level (e.g. critical, error, warning, info, debug, trace) |
| DAILY_SCHEDULE_TIME | 23:45 | The time of day to run the daily tasks. |
| Variables | Default | Description |
| ----------------------------- | :-------------------: | --------------------------------------------------------------------------------------------------------- |
| PUID | 911 | UserID permissions between host OS and container |
| PGID | 911 | GroupID permissions between host OS and container |
| DEFAULT_GROUP | Home | The default group for users |
| BASE_URL | http://localhost:8080 | Used for Notifications |
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
| API_DOCS | True | Turns on/off access to the API documentation locally |
| TZ | UTC | Must be set to get correct date/time on the server |
| ALLOW_SIGNUP<super>\*</super> | false | Allow user sign-up without token |
| LOG_CONFIG_OVERRIDE | | Override the config for logging with a custom path |
| LOG_LEVEL | info | Logging level (e.g. critical, error, warning, info, debug, trace) |
| DAILY_SCHEDULE_TIME | 23:45 | The time of day to run daily server tasks, in HH:MM format. Use the server's local time, *not* UTC |
<super>\*</super> Starting in v1.4.0 this was changed to default to `false` as part of a security review of the application.

View File

@ -1,6 +1,10 @@
import logging
import secrets
from datetime import datetime, timezone
from pathlib import Path
from typing import NamedTuple
from dateutil.tz import tzlocal
from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
@ -9,6 +13,11 @@ from mealie.core.settings.themes import Theme
from .db_providers import AbstractDBProvider, db_provider_factory
class ScheduleTime(NamedTuple):
hour: int
minute: int
def determine_secrets(data_dir: Path, production: bool) -> str:
if not production:
return "shh-secret-test-key"
@ -58,6 +67,44 @@ class AppSettings(BaseSettings):
ALLOW_SIGNUP: bool = False
DAILY_SCHEDULE_TIME: str = "23:45"
"""Local server time, in HH:MM format. See `DAILY_SCHEDULE_TIME_UTC` for the parsed UTC equivalent"""
_logger: logging.Logger | None = None
@property
def logger(self) -> logging.Logger:
if self._logger is None:
# lazy load the logger, since get_logger depends on the settings being loaded
from mealie.core.root_logger import get_logger
self._logger = get_logger()
return self._logger
@property
def DAILY_SCHEDULE_TIME_UTC(self) -> ScheduleTime:
"""The DAILY_SCHEDULE_TIME in UTC, parsed into hours and minutes"""
# parse DAILY_SCHEDULE_TIME into hours and minutes
try:
hour_str, minute_str = self.DAILY_SCHEDULE_TIME.split(":")
local_hour = int(hour_str)
local_minute = int(minute_str)
except ValueError:
local_hour = 23
local_minute = 45
self.logger.exception(
f"Unable to parse {self.DAILY_SCHEDULE_TIME=} as HH:MM; defaulting to {local_hour}:{local_minute}"
)
# DAILY_SCHEDULE_TIME is in local time, so we convert it to UTC
local_tz = tzlocal()
now = datetime.now(local_tz)
local_time = now.replace(hour=local_hour, minute=local_minute)
utc_time = local_time.astimezone(timezone.utc)
self.logger.debug(f"Local time: {local_hour}:{local_minute} | UTC time: {utc_time.hour}:{utc_time.minute}")
return ScheduleTime(utc_time.hour, utc_time.minute)
# ===============================================
# Security Configuration

View File

@ -29,20 +29,12 @@ class SchedulerService:
async def schedule_daily():
now = datetime.now(timezone.utc)
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
daily_schedule_time = get_app_settings().DAILY_SCHEDULE_TIME_UTC
logger.debug(f"Current time is {now} and DAILY_SCHEDULE_TIME (in UTC) is {daily_schedule_time}")
next_schedule = now.replace(hour=hour_target, minute=minute_target, second=0, microsecond=0)
next_schedule = now.replace(
hour=daily_schedule_time.hour, minute=daily_schedule_time.minute, second=0, microsecond=0
)
delta = next_schedule - now
if delta < timedelta(0):
next_schedule = next_schedule + timedelta(days=1)
@ -61,12 +53,6 @@ async def schedule_daily():
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()