mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-07-09 03:04:54 -04:00
fix: Don't load from secrets dir if nonexistent or inaccessible (#4002)
Co-authored-by: Michael Genson <71845777+michael-genson@users.noreply.github.com>
This commit is contained in:
parent
f11af52d30
commit
65ece35966
@ -4,9 +4,12 @@ from pathlib import Path
|
|||||||
|
|
||||||
import dotenv
|
import dotenv
|
||||||
|
|
||||||
from mealie.core.settings import app_settings_constructor
|
from mealie.core.settings import (
|
||||||
|
AppDirectories,
|
||||||
from .settings import AppDirectories, AppSettings
|
AppLoggingSettings,
|
||||||
|
AppSettings,
|
||||||
|
app_settings_constructor,
|
||||||
|
)
|
||||||
|
|
||||||
CWD = Path(__file__).parent
|
CWD = Path(__file__).parent
|
||||||
BASE_DIR = CWD.parent.parent
|
BASE_DIR = CWD.parent.parent
|
||||||
@ -38,3 +41,8 @@ def get_app_dirs() -> AppDirectories:
|
|||||||
@lru_cache
|
@lru_cache
|
||||||
def get_app_settings() -> AppSettings:
|
def get_app_settings() -> AppSettings:
|
||||||
return app_settings_constructor(env_file=ENV, production=PRODUCTION, data_dir=determine_data_dir())
|
return app_settings_constructor(env_file=ENV, production=PRODUCTION, data_dir=determine_data_dir())
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache
|
||||||
|
def get_logging_settings() -> AppLoggingSettings:
|
||||||
|
return AppLoggingSettings(PRODUCTION=PRODUCTION)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from .config import get_app_dirs, get_app_settings
|
from .config import get_app_dirs, get_logging_settings
|
||||||
from .logger.config import configured_logger
|
from .logger.config import configured_logger
|
||||||
|
|
||||||
__root_logger: None | logging.Logger = None
|
__root_logger: None | logging.Logger = None
|
||||||
@ -18,25 +18,25 @@ def get_logger(module=None) -> logging.Logger:
|
|||||||
global __root_logger
|
global __root_logger
|
||||||
|
|
||||||
if __root_logger is None:
|
if __root_logger is None:
|
||||||
app_settings = get_app_settings()
|
app_logging_settings = get_logging_settings()
|
||||||
|
|
||||||
mode = "development"
|
mode = "development"
|
||||||
|
|
||||||
if app_settings.TESTING:
|
if app_logging_settings.TESTING:
|
||||||
mode = "testing"
|
mode = "testing"
|
||||||
elif app_settings.PRODUCTION:
|
elif app_logging_settings.PRODUCTION:
|
||||||
mode = "production"
|
mode = "production"
|
||||||
|
|
||||||
dirs = get_app_dirs()
|
dirs = get_app_dirs()
|
||||||
|
|
||||||
substitutions = {
|
substitutions = {
|
||||||
"DATA_DIR": dirs.DATA_DIR.as_posix(),
|
"DATA_DIR": dirs.DATA_DIR.as_posix(),
|
||||||
"LOG_LEVEL": app_settings.LOG_LEVEL.upper(),
|
"LOG_LEVEL": app_logging_settings.LOG_LEVEL.upper(),
|
||||||
}
|
}
|
||||||
|
|
||||||
__root_logger = configured_logger(
|
__root_logger = configured_logger(
|
||||||
mode=mode,
|
mode=mode,
|
||||||
config_override=app_settings.LOG_CONFIG_OVERRIDE,
|
config_override=app_logging_settings.LOG_CONFIG_OVERRIDE,
|
||||||
substitutions=substitutions,
|
substitutions=substitutions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
import secrets
|
import secrets
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -34,10 +35,54 @@ def determine_secrets(data_dir: Path, production: bool) -> str:
|
|||||||
return new_secret
|
return new_secret
|
||||||
|
|
||||||
|
|
||||||
class AppSettings(BaseSettings):
|
def get_secrets_dir() -> str | None:
|
||||||
|
"""
|
||||||
|
Returns a directory to load secret settings from, or `None` if the secrets
|
||||||
|
directory does not exist or cannot be accessed.
|
||||||
|
"""
|
||||||
|
# Avoid a circular import by importing here instead of at the file's top-level.
|
||||||
|
# get_logger -> AppSettings -> get_logger
|
||||||
|
from mealie.core.root_logger import get_logger
|
||||||
|
|
||||||
|
logger = get_logger()
|
||||||
|
|
||||||
|
secrets_dir = "/run/secrets"
|
||||||
|
|
||||||
|
# Check that the secrets directory exists.
|
||||||
|
if not os.path.exists(secrets_dir):
|
||||||
|
logger.warning(f"Secrets directory '{secrets_dir}' does not exist")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Likewise, check we have permission to read from the secrets directory.
|
||||||
|
if not os.access(secrets_dir, os.R_OK):
|
||||||
|
logger.warning(f"Secrets directory '{secrets_dir}' cannot be read from. Check permissions")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# The secrets directory exists and can be accessed.
|
||||||
|
return secrets_dir
|
||||||
|
|
||||||
|
|
||||||
|
class AppLoggingSettings(BaseSettings):
|
||||||
|
"""
|
||||||
|
Subset of AppSettings to only access logging-related settings.
|
||||||
|
|
||||||
|
This is separated out from AppSettings to allow logging during construction
|
||||||
|
of AppSettings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
TESTING: bool = False
|
||||||
|
PRODUCTION: bool
|
||||||
|
|
||||||
|
LOG_CONFIG_OVERRIDE: Path | None = None
|
||||||
|
"""path to custom logging configuration file"""
|
||||||
|
|
||||||
|
LOG_LEVEL: str = "info"
|
||||||
|
"""corresponds to standard Python log levels"""
|
||||||
|
|
||||||
|
|
||||||
|
class AppSettings(AppLoggingSettings):
|
||||||
theme: Theme = Theme()
|
theme: Theme = Theme()
|
||||||
|
|
||||||
PRODUCTION: bool
|
|
||||||
BASE_URL: str = "http://localhost:8080"
|
BASE_URL: str = "http://localhost:8080"
|
||||||
"""trailing slashes are trimmed (ex. `http://localhost:8080/` becomes ``http://localhost:8080`)"""
|
"""trailing slashes are trimmed (ex. `http://localhost:8080/` becomes ``http://localhost:8080`)"""
|
||||||
|
|
||||||
@ -56,12 +101,6 @@ class AppSettings(BaseSettings):
|
|||||||
|
|
||||||
SECRET: str
|
SECRET: str
|
||||||
|
|
||||||
LOG_CONFIG_OVERRIDE: Path | None = None
|
|
||||||
"""path to custom logging configuration file"""
|
|
||||||
|
|
||||||
LOG_LEVEL: str = "info"
|
|
||||||
"""corresponds to standard Python log levels"""
|
|
||||||
|
|
||||||
GIT_COMMIT_HASH: str = "unknown"
|
GIT_COMMIT_HASH: str = "unknown"
|
||||||
|
|
||||||
ALLOW_SIGNUP: bool = False
|
ALLOW_SIGNUP: bool = False
|
||||||
@ -69,17 +108,13 @@ class AppSettings(BaseSettings):
|
|||||||
DAILY_SCHEDULE_TIME: str = "23:45"
|
DAILY_SCHEDULE_TIME: str = "23:45"
|
||||||
"""Local server time, in HH:MM format. See `DAILY_SCHEDULE_TIME_UTC` for the parsed UTC equivalent"""
|
"""Local server time, in HH:MM format. See `DAILY_SCHEDULE_TIME_UTC` for the parsed UTC equivalent"""
|
||||||
|
|
||||||
_logger: logging.Logger | None = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def logger(self) -> logging.Logger:
|
def logger(self) -> logging.Logger:
|
||||||
if self._logger is None:
|
# Avoid a circular import by importing here instead of at the file's top-level.
|
||||||
# lazy load the logger, since get_logger depends on the settings being loaded
|
# get_logger -> AppSettings -> get_logger
|
||||||
from mealie.core.root_logger import get_logger
|
from mealie.core.root_logger import get_logger
|
||||||
|
|
||||||
self._logger = get_logger()
|
return get_logger()
|
||||||
|
|
||||||
return self._logger
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def DAILY_SCHEDULE_TIME_UTC(self) -> ScheduleTime:
|
def DAILY_SCHEDULE_TIME_UTC(self) -> ScheduleTime:
|
||||||
@ -303,11 +338,7 @@ class AppSettings(BaseSettings):
|
|||||||
"""Validates OpenAI settings are all set"""
|
"""Validates OpenAI settings are all set"""
|
||||||
return bool(self.OPENAI_API_KEY and self.OPENAI_MODEL)
|
return bool(self.OPENAI_API_KEY and self.OPENAI_MODEL)
|
||||||
|
|
||||||
# ===============================================
|
model_config = SettingsConfigDict(arbitrary_types_allowed=True, extra="allow")
|
||||||
# Testing Config
|
|
||||||
|
|
||||||
TESTING: bool = False
|
|
||||||
model_config = SettingsConfigDict(arbitrary_types_allowed=True, extra="allow", secrets_dir="/run/secrets")
|
|
||||||
|
|
||||||
|
|
||||||
def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, env_encoding="utf-8") -> AppSettings:
|
def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, env_encoding="utf-8") -> AppSettings:
|
||||||
@ -319,6 +350,9 @@ def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, e
|
|||||||
app_settings = AppSettings(
|
app_settings = AppSettings(
|
||||||
_env_file=env_file, # type: ignore
|
_env_file=env_file, # type: ignore
|
||||||
_env_file_encoding=env_encoding, # type: ignore
|
_env_file_encoding=env_encoding, # type: ignore
|
||||||
|
# `get_secrets_dir` must be called here rather than within `AppSettings`
|
||||||
|
# to avoid a circular import.
|
||||||
|
_secrets_dir=get_secrets_dir(), # type: ignore
|
||||||
**{"SECRET": determine_secrets(data_dir, production)},
|
**{"SECRET": determine_secrets(data_dir, production)},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user