mirror of
https://github.com/rommapp/romm.git
synced 2025-12-22 10:27:13 +00:00
Use default config values when config.yml not mount + show warning
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Final, NotRequired, TypedDict
|
||||
|
||||
@@ -18,10 +19,7 @@ from config import (
|
||||
ROMM_BASE_PATH,
|
||||
ROMM_DB_DRIVER,
|
||||
)
|
||||
from exceptions.config_exceptions import (
|
||||
ConfigNotReadableException,
|
||||
ConfigNotWritableException,
|
||||
)
|
||||
from exceptions.config_exceptions import ConfigNotWritableException
|
||||
from logger.formatter import BLUE
|
||||
from logger.formatter import highlight as hl
|
||||
from logger.logger import log
|
||||
@@ -47,6 +45,7 @@ EjsOption = dict[str, str] # option_name -> option_value
|
||||
|
||||
|
||||
class Config:
|
||||
CONFIG_FILE_MOUNTED: bool
|
||||
EXCLUDED_PLATFORMS: list[str]
|
||||
EXCLUDED_SINGLE_EXT: list[str]
|
||||
EXCLUDED_SINGLE_FILES: list[str]
|
||||
@@ -69,13 +68,16 @@ class Config:
|
||||
|
||||
|
||||
class ConfigManager:
|
||||
"""Parse and load the user configuration from the config.yml file
|
||||
"""
|
||||
Parse and load the user configuration from the config.yml file.
|
||||
If config.yml is not found, uses default configuration values.
|
||||
|
||||
Raises:
|
||||
FileNotFoundError: Raises an error if the config.yml is not found
|
||||
The config file will be created automatically when configuration is updated.
|
||||
"""
|
||||
|
||||
_self = None
|
||||
_raw_config: dict = {}
|
||||
_config_file_mounted: bool = False
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._self is None:
|
||||
@@ -88,14 +90,22 @@ class ConfigManager:
|
||||
self.config_file = config_file
|
||||
|
||||
try:
|
||||
self.get_config()
|
||||
except ConfigNotReadableException as e:
|
||||
log.critical(e.message)
|
||||
sys.exit(5)
|
||||
with open(self.config_file, "r+") as cf:
|
||||
self._raw_config = yaml.load(cf, Loader=SafeLoader) or {}
|
||||
self._config_file_mounted = True
|
||||
except (FileNotFoundError, PermissionError):
|
||||
log.critical(
|
||||
"Config file not found or not writable, any changes made to the configuration will not persist after the application restarts."
|
||||
)
|
||||
self._config_file_mounted = False
|
||||
|
||||
# Set the config to default values
|
||||
self._parse_config()
|
||||
self._validate_config()
|
||||
|
||||
@staticmethod
|
||||
def get_db_engine() -> URL:
|
||||
"""Builds the database connection string depending on the defined database in the config.yml file
|
||||
"""Builds the database connection string using environment variables
|
||||
|
||||
Returns:
|
||||
str: database connection string
|
||||
@@ -139,6 +149,7 @@ class ConfigManager:
|
||||
"""Parses each entry in the config.yml"""
|
||||
|
||||
self.config = Config(
|
||||
CONFIG_FILE_MOUNTED=self._config_file_mounted,
|
||||
EXCLUDED_PLATFORMS=pydash.get(self._raw_config, "exclude.platforms", []),
|
||||
EXCLUDED_SINGLE_EXT=[
|
||||
e.lower()
|
||||
@@ -193,6 +204,22 @@ class ConfigManager:
|
||||
|
||||
return controls
|
||||
|
||||
def _format_ejs_controls_for_yaml(
|
||||
self,
|
||||
) -> dict[str, dict[int, dict[int, EjsControlsButton]]]:
|
||||
"""Format EJS controls back to YAML structure for saving"""
|
||||
yaml_controls = {}
|
||||
|
||||
for core, controls in self.config.EJS_CONTROLS.items():
|
||||
yaml_controls[core] = {
|
||||
0: controls["_0"],
|
||||
1: controls["_1"],
|
||||
2: controls["_2"],
|
||||
3: controls["_3"],
|
||||
}
|
||||
|
||||
return yaml_controls
|
||||
|
||||
def _validate_config(self):
|
||||
"""Validates the config.yml file"""
|
||||
if not isinstance(self.config.EXCLUDED_PLATFORMS, list):
|
||||
@@ -322,15 +349,23 @@ class ConfigManager:
|
||||
sys.exit(3)
|
||||
|
||||
def get_config(self) -> Config:
|
||||
with open(self.config_file) as config_file:
|
||||
self._raw_config = yaml.load(config_file, Loader=SafeLoader) or {}
|
||||
try:
|
||||
with open(self.config_file, "r+") as config_file:
|
||||
self._raw_config = yaml.load(config_file, Loader=SafeLoader) or {}
|
||||
except (FileNotFoundError, PermissionError):
|
||||
log.debug("Config file not found or not writable")
|
||||
pass
|
||||
|
||||
self._parse_config()
|
||||
self._validate_config()
|
||||
|
||||
return self.config
|
||||
|
||||
def update_config_file(self) -> None:
|
||||
def _update_config_file(self) -> None:
|
||||
if not self._config_file_mounted:
|
||||
log.warning("Config file not mounted, skipping config file update")
|
||||
raise ConfigNotWritableException
|
||||
|
||||
self._raw_config = {
|
||||
"exclude": {
|
||||
"platforms": self.config.EXCLUDED_PLATFORMS,
|
||||
@@ -356,15 +391,22 @@ class ConfigManager:
|
||||
"platforms": self.config.PLATFORMS_BINDING,
|
||||
"versions": self.config.PLATFORMS_VERSIONS,
|
||||
},
|
||||
"emulatorjs": {
|
||||
"debug": self.config.EJS_DEBUG,
|
||||
"cache_limit": self.config.EJS_CACHE_LIMIT,
|
||||
"settings": self.config.EJS_SETTINGS,
|
||||
"controls": self._format_ejs_controls_for_yaml(),
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
with open(self.config_file, "w") as config_file:
|
||||
# Ensure the config directory exists
|
||||
os.makedirs(os.path.dirname(self.config_file), exist_ok=True)
|
||||
|
||||
with open(self.config_file, "w+") as config_file:
|
||||
yaml.dump(self._raw_config, config_file)
|
||||
except FileNotFoundError:
|
||||
self._raw_config = {}
|
||||
except PermissionError as exc:
|
||||
self._raw_config = {}
|
||||
log.critical("Config file not writable, skipping config file update")
|
||||
raise ConfigNotWritableException from exc
|
||||
|
||||
def add_platform_binding(self, fs_slug: str, slug: str) -> None:
|
||||
@@ -375,7 +417,7 @@ class ConfigManager:
|
||||
|
||||
platform_bindings[fs_slug] = slug
|
||||
self.config.PLATFORMS_BINDING = platform_bindings
|
||||
self.update_config_file()
|
||||
self._update_config_file()
|
||||
|
||||
def remove_platform_binding(self, fs_slug: str) -> None:
|
||||
platform_bindings = self.config.PLATFORMS_BINDING
|
||||
@@ -386,7 +428,7 @@ class ConfigManager:
|
||||
pass
|
||||
|
||||
self.config.PLATFORMS_BINDING = platform_bindings
|
||||
self.update_config_file()
|
||||
self._update_config_file()
|
||||
|
||||
def add_platform_version(self, fs_slug: str, slug: str) -> None:
|
||||
platform_versions = self.config.PLATFORMS_VERSIONS
|
||||
@@ -396,7 +438,7 @@ class ConfigManager:
|
||||
|
||||
platform_versions[fs_slug] = slug
|
||||
self.config.PLATFORMS_VERSIONS = platform_versions
|
||||
self.update_config_file()
|
||||
self._update_config_file()
|
||||
|
||||
def remove_platform_version(self, fs_slug: str) -> None:
|
||||
platform_versions = self.config.PLATFORMS_VERSIONS
|
||||
@@ -407,7 +449,7 @@ class ConfigManager:
|
||||
pass
|
||||
|
||||
self.config.PLATFORMS_VERSIONS = platform_versions
|
||||
self.update_config_file()
|
||||
self._update_config_file()
|
||||
|
||||
def add_exclusion(self, exclusion_type: str, exclusion_value: str):
|
||||
config_item = self.config.__getattribute__(exclusion_type)
|
||||
@@ -419,7 +461,7 @@ class ConfigManager:
|
||||
|
||||
config_item.append(exclusion_value)
|
||||
self.config.__setattr__(exclusion_type, config_item)
|
||||
self.update_config_file()
|
||||
self._update_config_file()
|
||||
|
||||
def remove_exclusion(self, exclusion_type: str, exclusion_value: str):
|
||||
config_item = self.config.__getattribute__(exclusion_type)
|
||||
@@ -430,7 +472,7 @@ class ConfigManager:
|
||||
pass
|
||||
|
||||
self.config.__setattr__(exclusion_type, config_item)
|
||||
self.update_config_file()
|
||||
self._update_config_file()
|
||||
|
||||
|
||||
config_manager = ConfigManager()
|
||||
|
||||
@@ -3,10 +3,7 @@ from fastapi import HTTPException, Request, status
|
||||
from config.config_manager import config_manager as cm
|
||||
from decorators.auth import protected_route
|
||||
from endpoints.responses.config import ConfigResponse
|
||||
from exceptions.config_exceptions import (
|
||||
ConfigNotReadableException,
|
||||
ConfigNotWritableException,
|
||||
)
|
||||
from exceptions.config_exceptions import ConfigNotWritableException
|
||||
from handler.auth.constants import Scope
|
||||
from logger.logger import log
|
||||
from utils.router import APIRouter
|
||||
@@ -25,27 +22,22 @@ def get_config() -> ConfigResponse:
|
||||
ConfigResponse: RomM's configuration
|
||||
"""
|
||||
|
||||
try:
|
||||
cfg = cm.get_config()
|
||||
return ConfigResponse(
|
||||
EXCLUDED_PLATFORMS=cfg.EXCLUDED_PLATFORMS,
|
||||
EXCLUDED_SINGLE_EXT=cfg.EXCLUDED_SINGLE_EXT,
|
||||
EXCLUDED_SINGLE_FILES=cfg.EXCLUDED_SINGLE_FILES,
|
||||
EXCLUDED_MULTI_FILES=cfg.EXCLUDED_MULTI_FILES,
|
||||
EXCLUDED_MULTI_PARTS_EXT=cfg.EXCLUDED_MULTI_PARTS_EXT,
|
||||
EXCLUDED_MULTI_PARTS_FILES=cfg.EXCLUDED_MULTI_PARTS_FILES,
|
||||
PLATFORMS_BINDING=cfg.PLATFORMS_BINDING,
|
||||
PLATFORMS_VERSIONS=cfg.PLATFORMS_VERSIONS,
|
||||
EJS_DEBUG=cfg.EJS_DEBUG,
|
||||
EJS_CACHE_LIMIT=cfg.EJS_CACHE_LIMIT,
|
||||
EJS_CONTROLS=cfg.EJS_CONTROLS,
|
||||
EJS_SETTINGS=cfg.EJS_SETTINGS,
|
||||
)
|
||||
except ConfigNotReadableException as exc:
|
||||
log.critical(exc.message)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.message
|
||||
) from exc
|
||||
cfg = cm.get_config()
|
||||
return ConfigResponse(
|
||||
CONFIG_FILE_MOUNTED=cfg.CONFIG_FILE_MOUNTED,
|
||||
EXCLUDED_PLATFORMS=cfg.EXCLUDED_PLATFORMS,
|
||||
EXCLUDED_SINGLE_EXT=cfg.EXCLUDED_SINGLE_EXT,
|
||||
EXCLUDED_SINGLE_FILES=cfg.EXCLUDED_SINGLE_FILES,
|
||||
EXCLUDED_MULTI_FILES=cfg.EXCLUDED_MULTI_FILES,
|
||||
EXCLUDED_MULTI_PARTS_EXT=cfg.EXCLUDED_MULTI_PARTS_EXT,
|
||||
EXCLUDED_MULTI_PARTS_FILES=cfg.EXCLUDED_MULTI_PARTS_FILES,
|
||||
PLATFORMS_BINDING=cfg.PLATFORMS_BINDING,
|
||||
PLATFORMS_VERSIONS=cfg.PLATFORMS_VERSIONS,
|
||||
EJS_DEBUG=cfg.EJS_DEBUG,
|
||||
EJS_CACHE_LIMIT=cfg.EJS_CACHE_LIMIT,
|
||||
EJS_CONTROLS=cfg.EJS_CONTROLS,
|
||||
EJS_SETTINGS=cfg.EJS_SETTINGS,
|
||||
)
|
||||
|
||||
|
||||
@protected_route(router.post, "/system/platforms", [Scope.PLATFORMS_WRITE])
|
||||
|
||||
@@ -4,6 +4,7 @@ from config.config_manager import EjsControls
|
||||
|
||||
|
||||
class ConfigResponse(TypedDict):
|
||||
CONFIG_FILE_MOUNTED: bool
|
||||
EXCLUDED_PLATFORMS: list[str]
|
||||
EXCLUDED_SINGLE_EXT: list[str]
|
||||
EXCLUDED_SINGLE_FILES: list[str]
|
||||
|
||||
@@ -1,12 +1,3 @@
|
||||
class ConfigNotReadableException(Exception):
|
||||
def __init__(self):
|
||||
self.message = "Config file can't be read. Check config.yml permissions"
|
||||
super().__init__(self.message)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.message
|
||||
|
||||
|
||||
class ConfigNotWritableException(Exception):
|
||||
def __init__(self):
|
||||
self.message = "Config file is not writable. Check config.yml permissions"
|
||||
|
||||
@@ -26,7 +26,7 @@ services:
|
||||
- romm_redis_data:/redis-data # Cached data for background tasks
|
||||
- /path/to/library:/romm/library # Your game library. Check https://docs.romm.app/latest/Getting-Started/Folder-Structure/ for more details.
|
||||
- /path/to/assets:/romm/assets # Uploaded saves, states, etc.
|
||||
- /path/to/config:/romm/config # Path where config.yml is stored
|
||||
- /path/to/config:/romm/config # (Optional) Path where config.yml is stored
|
||||
ports:
|
||||
- 80:8080
|
||||
depends_on:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
/* eslint-disable */
|
||||
import type { EjsControls } from './EjsControls';
|
||||
export type ConfigResponse = {
|
||||
CONFIG_FILE_MOUNTED: boolean;
|
||||
EXCLUDED_PLATFORMS: Array<string>;
|
||||
EXCLUDED_SINGLE_EXT: Array<string>;
|
||||
EXCLUDED_SINGLE_FILES: Array<string>;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import CreateExclusionDialog from "@/components/Settings/LibraryManagement/Config/Dialog/CreateExclusion.vue";
|
||||
@@ -9,40 +10,41 @@ import storeConfig from "@/stores/config";
|
||||
|
||||
const { t } = useI18n();
|
||||
const configStore = storeConfig();
|
||||
const { config } = storeToRefs(configStore);
|
||||
const authStore = storeAuth();
|
||||
const exclusions = [
|
||||
{
|
||||
set: configStore.config.EXCLUDED_PLATFORMS,
|
||||
set: config.value.EXCLUDED_PLATFORMS,
|
||||
title: t("common.platform"),
|
||||
icon: "mdi-controller-off",
|
||||
type: "EXCLUDED_PLATFORMS",
|
||||
},
|
||||
{
|
||||
set: configStore.config.EXCLUDED_SINGLE_FILES,
|
||||
set: config.value.EXCLUDED_SINGLE_FILES,
|
||||
title: t("settings.excluded-single-rom-files"),
|
||||
icon: "mdi-file-document-remove-outline",
|
||||
type: "EXCLUDED_SINGLE_FILES",
|
||||
},
|
||||
{
|
||||
set: configStore.config.EXCLUDED_SINGLE_EXT,
|
||||
set: config.value.EXCLUDED_SINGLE_EXT,
|
||||
title: t("settings.excluded-single-rom-extensions"),
|
||||
icon: "mdi-file-document-remove-outline",
|
||||
type: "EXCLUDED_SINGLE_EXT",
|
||||
},
|
||||
{
|
||||
set: configStore.config.EXCLUDED_MULTI_FILES,
|
||||
set: config.value.EXCLUDED_MULTI_FILES,
|
||||
title: t("settings.excluded-multi-rom-files"),
|
||||
icon: "mdi-file-document-remove-outline",
|
||||
type: "EXCLUDED_MULTI_FILES",
|
||||
},
|
||||
{
|
||||
set: configStore.config.EXCLUDED_MULTI_PARTS_FILES,
|
||||
set: config.value.EXCLUDED_MULTI_PARTS_FILES,
|
||||
title: t("settings.excluded-multi-rom-parts-files"),
|
||||
icon: "mdi-file-document-remove-outline",
|
||||
type: "EXCLUDED_MULTI_PARTS_FILES",
|
||||
},
|
||||
{
|
||||
set: configStore.config.EXCLUDED_MULTI_PARTS_EXT,
|
||||
set: config.value.EXCLUDED_MULTI_PARTS_EXT,
|
||||
title: t("settings.excluded-multi-rom-parts-extensions"),
|
||||
icon: "mdi-file-document-remove-outline",
|
||||
type: "EXCLUDED_MULTI_PARTS_EXT",
|
||||
@@ -61,6 +63,7 @@ const editable = ref(false);
|
||||
variant="text"
|
||||
icon="mdi-cog"
|
||||
@click="editable = !editable"
|
||||
:disabled="!config.CONFIG_FILE_MOUNTED"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
|
||||
@@ -48,6 +48,7 @@ const editable = ref(false);
|
||||
variant="text"
|
||||
icon="mdi-cog"
|
||||
@click="editable = !editable"
|
||||
:disabled="!config.CONFIG_FILE_MOUNTED"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
|
||||
@@ -52,6 +52,7 @@ const editable = ref(false);
|
||||
variant="text"
|
||||
icon="mdi-cog"
|
||||
@click="editable = !editable"
|
||||
:disabled="!config.CONFIG_FILE_MOUNTED"
|
||||
/>
|
||||
</template>
|
||||
<template #content>
|
||||
|
||||
@@ -12,6 +12,7 @@ type ExclusionTypes =
|
||||
| "EXCLUDED_MULTI_PARTS_FILES";
|
||||
|
||||
const defaultConfig = {
|
||||
CONFIG_FILE_MOUNTED: false,
|
||||
EXCLUDED_PLATFORMS: [],
|
||||
EXCLUDED_SINGLE_EXT: [],
|
||||
EXCLUDED_SINGLE_FILES: [],
|
||||
|
||||
@@ -13,6 +13,7 @@ import PlatformVersions from "@/components/Settings/LibraryManagement/Config/Pla
|
||||
import GameTable from "@/components/common/Game/VirtualTable.vue";
|
||||
import MissingFromFSIcon from "@/components/common/MissingFromFSIcon.vue";
|
||||
import PlatformIcon from "@/components/common/Platform/PlatformIcon.vue";
|
||||
import storeConfig from "@/stores/config";
|
||||
import storeGalleryFilter from "@/stores/galleryFilter";
|
||||
import storeGalleryView from "@/stores/galleryView";
|
||||
import storePlatforms from "@/stores/platforms";
|
||||
@@ -21,6 +22,8 @@ import type { Events } from "@/types/emitter";
|
||||
|
||||
const { t } = useI18n();
|
||||
const tab = ref<"config" | "missing">("config");
|
||||
const configStore = storeConfig();
|
||||
const { config } = storeToRefs(configStore);
|
||||
const romsStore = storeRoms();
|
||||
const { allRoms, fetchingRoms, fetchTotalRoms, filteredRoms } =
|
||||
storeToRefs(romsStore);
|
||||
@@ -195,6 +198,18 @@ onUnmounted(() => {
|
||||
</v-tabs>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-alert
|
||||
v-if="!config.CONFIG_FILE_MOUNTED"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
class="my-2"
|
||||
>
|
||||
<template #title> Configuration file not mounted </template>
|
||||
<template #text>
|
||||
The config.yml file is not mounted or writable. Any changes made to
|
||||
the configuration will not persist after the application restarts.
|
||||
</template>
|
||||
</v-alert>
|
||||
<v-tabs-window v-model="tab">
|
||||
<v-tabs-window-item value="config">
|
||||
<PlatformBinding class="mt-2" />
|
||||
|
||||
Reference in New Issue
Block a user