Files
Anthias/settings.py
Viktor Petersson c6bf161d83 fix: handle missing config dir gracefully, improve server wait
- Catch OSError in AnthiasSettings.__init__ when config directory
  can't be created (e.g., Docker volume permissions during startup)
- Change generate-openapi-schema wait to use curl on port 8000
  instead of `import django`, ensuring start_server.sh has completed

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 05:11:50 +00:00

180 lines
5.5 KiB
Python

import configparser
import hashlib
import logging
from collections import UserDict
from os import getenv, makedirs, path
from typing import Any
from lib.auth import BasicAuth, NoAuth
CONFIG_DIR = '.screenly/'
CONFIG_FILE = 'screenly.conf'
DEFAULTS = {
'main': {
'analytics_opt_out': False,
'assetdir': 'screenly_assets',
'database': CONFIG_DIR + 'screenly.db',
'date_format': 'mm/dd/yyyy',
'use_24_hour_clock': False,
'use_ssl': False,
'auth_backend': '',
'websocket_port': '9999',
'django_secret_key': '',
},
'viewer': {
'audio_output': 'hdmi',
'debug_logging': False,
'default_duration': 10,
'default_streaming_duration': '300',
'player_name': '',
'resolution': '1920x1080',
'show_splash': True,
'shuffle_playlist': False,
'verify_ssl': True,
'default_assets': False,
},
}
CONFIGURABLE_SETTINGS = DEFAULTS['viewer'].copy()
CONFIGURABLE_SETTINGS['use_24_hour_clock'] = DEFAULTS['main'][
'use_24_hour_clock'
]
CONFIGURABLE_SETTINGS['date_format'] = DEFAULTS['main']['date_format']
PORT = int(getenv('PORT', 8000))
LISTEN = getenv('LISTEN', '127.0.0.1')
# Initiate logging
logging.basicConfig(
level=logging.INFO,
format='%(message)s',
datefmt='%a, %d %b %Y %H:%M:%S',
)
# Silence urllib info messages ('Starting new HTTP connection')
# that are triggered by the remote url availability check in view_web
requests_log = logging.getLogger('requests')
requests_log.setLevel(logging.WARNING)
logging.debug('Starting viewer')
class AnthiasSettings(UserDict):
"""Anthias' Settings."""
def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.home = getenv('HOME')
self.conf_file = self.get_configfile()
self.auth_backends_list = [NoAuth(), BasicAuth(self)]
self.auth_backends = {}
for backend in self.auth_backends_list:
DEFAULTS.update(backend.config)
self.auth_backends[backend.name] = backend
if not path.isfile(self.conf_file):
logging.error(
'Config-file %s missing. Using defaults.', self.conf_file
)
self.use_defaults()
try:
self.save()
except OSError as e:
logging.warning(
'Could not save config file %s: %s',
self.conf_file,
e,
)
else:
self.load()
def _get(
self,
config: configparser.ConfigParser,
section: str,
field: str,
default: Any,
) -> None:
try:
if isinstance(default, bool):
self[field] = config.getboolean(section, field)
elif isinstance(default, int):
self[field] = config.getint(section, field)
else:
self[field] = config.get(section, field)
# Likely not a hashed password
if (
field == 'password'
and self[field] != ''
and len(self[field]) != 64
):
# Hash the original password.
self[field] = hashlib.sha256(self[field]).hexdigest()
except configparser.Error as e:
logging.debug(
"Could not parse setting '%s.%s': %s. "
"Using default value: '%s'.",
section,
field,
str(e),
default,
)
self[field] = default
if field in ['database', 'assetdir']:
self[field] = path.join(self.home, self[field])
def _set(
self,
config: configparser.ConfigParser,
section: str,
field: str,
default: Any,
) -> None:
if isinstance(default, bool):
config.set(
section, field, self.get(field, default) and 'on' or 'off'
)
else:
config.set(section, field, str(self.get(field, default)))
def load(self) -> None:
"""Loads the latest settings from screenly.conf into memory."""
logging.debug('Reading config-file...')
config = configparser.ConfigParser()
config.read(self.conf_file)
for section, defaults in DEFAULTS.items():
for field, default in defaults.items():
self._get(config, section, field, default)
def use_defaults(self) -> None:
for defaults in DEFAULTS.items():
for field, default in defaults[1].items():
self[field] = default
def save(self) -> None:
# Write new settings to disk.
makedirs(path.dirname(self.conf_file), exist_ok=True)
config = configparser.ConfigParser()
for section, defaults in DEFAULTS.items():
config.add_section(section)
for field, default in defaults.items():
self._set(config, section, field, default)
with open(self.conf_file, 'w') as f:
config.write(f)
self.load()
def get_configdir(self) -> str:
return path.join(self.home, CONFIG_DIR)
def get_configfile(self) -> str:
return path.join(self.home, CONFIG_DIR, CONFIG_FILE)
@property
def auth(self):
backend_name = self['auth_backend']
if backend_name in self.auth_backends:
return self.auth_backends[self['auth_backend']]
settings = AnthiasSettings()