Persist webhook settings under STATE_DIR across restarts.

Save WEBSERVICE_* values to webhook_settings.json when updated via POST /webhook-settings; load on startup so UI-saved config survives container recreation. Document behavior in README.

Made-with: Cursor
This commit is contained in:
KennyG 2026-04-02 15:30:58 -04:00
parent 6a3094ea19
commit 9c65979c77
2 changed files with 45 additions and 0 deletions

View file

@ -102,6 +102,8 @@ MeTube can call a generic webhook endpoint after download completion. The payloa
- `POST /webhook-settings` -> update runtime settings - `POST /webhook-settings` -> update runtime settings
- `POST /webhook-settings/test` -> send test payload and return endpoint response - `POST /webhook-settings/test` -> send test payload and return endpoint response
Settings saved via `POST /webhook-settings` are written to `{STATE_DIR}/webhook_settings.json` and reloaded on startup, so they survive container restarts. If this file is present, it overrides the `WEBSERVICE_*` environment defaults for the running process.
Example: Example:
```json ```json

View file

@ -20,6 +20,7 @@ from watchfiles import DefaultFilter, Change, awatch
from ytdl import DownloadQueueNotifier, DownloadQueue, Download from ytdl import DownloadQueueNotifier, DownloadQueue, Download
from subscriptions import SubscriptionManager, SubscriptionNotifier, SubscriptionInfo from subscriptions import SubscriptionManager, SubscriptionNotifier, SubscriptionInfo
from state_store import AtomicJsonStore
from yt_dlp.version import __version__ as yt_dlp_version from yt_dlp.version import __version__ as yt_dlp_version
log = logging.getLogger('main') log = logging.getLogger('main')
@ -169,6 +170,43 @@ class Config:
return (True, '') return (True, '')
config = Config() config = Config()
_webhook_settings_store = AtomicJsonStore(
os.path.join(config.STATE_DIR, 'webhook_settings.json'),
kind='webhook_settings',
)
def _load_persisted_webhook_settings() -> None:
"""Apply webhook endpoint settings saved under STATE_DIR (survives container restarts)."""
payload = _webhook_settings_store.load()
if not payload:
return
try:
if 'endpoint' in payload:
ep = str(payload['endpoint'] or '').strip()
if ep:
config.WEBSERVICE_ENDPOINT = ep
if 'timeout_sec' in payload:
ts = int(payload['timeout_sec'])
if ts > 0:
config.WEBSERVICE_TIMEOUT_SEC = ts
if 'enabled' in payload:
config.WEBSERVICE_ENABLED = bool(payload['enabled'])
except (TypeError, ValueError) as exc:
log.warning('Ignoring invalid persisted webhook settings: %s', exc)
def _save_persisted_webhook_settings() -> None:
_webhook_settings_store.save({
'enabled': bool(config.WEBSERVICE_ENABLED),
'endpoint': str(config.WEBSERVICE_ENDPOINT),
'timeout_sec': int(config.WEBSERVICE_TIMEOUT_SEC),
})
_load_persisted_webhook_settings()
# Align root logger level with Config (keeps a single source of truth). # Align root logger level with Config (keeps a single source of truth).
# This re-applies the log level after Config loads, in case LOGLEVEL was # This re-applies the log level after Config loads, in case LOGLEVEL was
# overridden by config file settings or differs from the environment variable. # overridden by config file settings or differs from the environment variable.
@ -712,6 +750,11 @@ async def set_webhook_settings(request):
config.WEBSERVICE_ENABLED = enabled config.WEBSERVICE_ENABLED = enabled
config.WEBSERVICE_ENDPOINT = endpoint config.WEBSERVICE_ENDPOINT = endpoint
config.WEBSERVICE_TIMEOUT_SEC = timeout_sec config.WEBSERVICE_TIMEOUT_SEC = timeout_sec
try:
_save_persisted_webhook_settings()
except OSError as exc:
log.exception('Failed to persist webhook settings to %s', _webhook_settings_store.path)
raise web.HTTPInternalServerError(reason=f'could not persist webhook settings: {exc}') from exc
return web.Response(text=serializer.encode({'status': 'ok'}), content_type='application/json') return web.Response(text=serializer.encode({'status': 'ok'}), content_type='application/json')
@routes.post(config.URL_PREFIX + 'webhook-settings/test') @routes.post(config.URL_PREFIX + 'webhook-settings/test')