e518bce922
Resolve all merge conflicts between our automation feature branch and current main (v0.163.0, 920 commits ahead). Conflicts resolved: - app/api/__init__.py: add automation_router alongside main's new routers (classification_rules, qr_auth, sessions, system_reset) - app/config.py: add main's new settings (dropbox_use_global_credentials, factory_reset_on_startup, enable_factory_reset) - app/models.py: add main's new models (ClassificationRuleModel, UserSession, QRLoginChallenge, SharePoint integration type) - app/utils/settings_service.py: merge automation_hooks_enabled with main's new metadata entries - docs/API.md: merge automation API docs with main's classification rules docs - docs/ConfigurationGuide.md: add factory reset settings - tests/conftest.py: import both AutomationHook and new main models Migration renumbered: - 037_add_automation_hooks → 040_add_automation_hooks - down_revision: 039_add_classification_rules (was 036_add_document_translation_fields) - Chain: 036 → 037 → 038 → 039 → 040 (automation hooks) For all non-automation files with conflicts, main's version was taken since our branch did not modify those files (conflicts were from a stale prior merge). Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> Agent-Logs-Url: https://github.com/christianlouis/DocuElevate/sessions/cb62f012-3b69-4415-835e-3857ce3e9f45
3422 lines
118 KiB
Python
3422 lines
118 KiB
Python
"""
|
||
Service for managing application settings with database persistence.
|
||
|
||
This module provides functionality to:
|
||
- Load settings from database with precedence over environment variables
|
||
- Save settings to database
|
||
- Get setting metadata (descriptions, types, categories)
|
||
"""
|
||
|
||
import logging
|
||
from typing import Any, Dict, List, Optional, Tuple
|
||
|
||
from sqlalchemy.exc import SQLAlchemyError
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.models import ApplicationSettings, SettingsAuditLog
|
||
from app.utils.ocr_provider import KNOWN_OCR_PROVIDERS
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# Define setting metadata for UI display
|
||
SETTING_METADATA = {
|
||
# Core Settings
|
||
"database_url": {
|
||
"category": "Core",
|
||
"description": "Database connection URL (e.g., sqlite:///path/to/db.sqlite). Use the Database Wizard for guided setup.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": True,
|
||
"restart_required": True,
|
||
"help_link": "/database-wizard",
|
||
"help_link_label": "Open Database Wizard",
|
||
},
|
||
"redis_url": {
|
||
"category": "Core",
|
||
"description": "Redis connection URL for Celery task queue",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": True,
|
||
"restart_required": True,
|
||
},
|
||
"db_pool_size": {
|
||
"category": "Core",
|
||
"description": (
|
||
"Number of persistent database connections kept in the pool per worker process. "
|
||
"Ignored for SQLite (which uses NullPool). Default: 10."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"db_max_overflow": {
|
||
"category": "Core",
|
||
"description": (
|
||
"Additional database connections allowed beyond db_pool_size under burst load. "
|
||
"Ignored for SQLite. Default: 20."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"db_pool_timeout": {
|
||
"category": "Core",
|
||
"description": (
|
||
"Seconds to wait for a database connection from the pool before raising a TimeoutError. "
|
||
"Ignored for SQLite. Default: 30."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"db_pool_recycle": {
|
||
"category": "Core",
|
||
"description": (
|
||
"Recycle (close and reopen) database connections after this many seconds "
|
||
"to avoid stale connections. Ignored for SQLite. Default: 1800."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"workdir": {
|
||
"category": "Core",
|
||
"description": "Working directory for file storage and processing",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": True,
|
||
"restart_required": True,
|
||
},
|
||
"external_hostname": {
|
||
"category": "Core",
|
||
"description": "External hostname for the application (e.g., docuelevate.example.com)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": True, # Required for OAuth redirects and external URLs
|
||
"restart_required": True,
|
||
},
|
||
"public_base_url": {
|
||
"category": "Core",
|
||
"description": (
|
||
"Full public base URL including scheme (e.g., https://docuelevate.example.com). "
|
||
"When set, overrides auto-detected URLs for OAuth redirect URIs. "
|
||
"Required when behind a reverse proxy that does not forward X-Forwarded-Proto."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"debug": {
|
||
"category": "Core",
|
||
"description": "Enable debug mode for verbose logging",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"gotenberg_url": {
|
||
"category": "Core",
|
||
"description": "Gotenberg service URL for document conversion",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": True,
|
||
"restart_required": True,
|
||
},
|
||
# Authentication Settings
|
||
"auth_enabled": {
|
||
"category": "Authentication",
|
||
"description": "Enable authentication for the application",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"multi_user_enabled": {
|
||
"category": "Authentication",
|
||
"description": (
|
||
"Enable multi-user mode with individual document spaces per user. "
|
||
"Each user sees only their own documents, uploads, and search results. "
|
||
"Requires auth_enabled=True."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"default_daily_upload_limit": {
|
||
"category": "Authentication",
|
||
"description": (
|
||
"Default maximum document uploads allowed per user per day in multi-user mode. Set to 0 for unlimited."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"unowned_docs_visible_to_all": {
|
||
"category": "Authentication",
|
||
"description": (
|
||
"In multi-user mode, controls whether documents without an owner are visible to all users. "
|
||
"When True, unowned documents appear alongside each user's own files. "
|
||
"When False, only admins can see unowned documents."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"default_owner_id": {
|
||
"category": "Authentication",
|
||
"description": (
|
||
"Automatically assigns this owner ID to newly ingested documents that would otherwise "
|
||
"have no owner. Start typing to search existing users, or leave empty to keep documents "
|
||
"unowned until claimed."
|
||
),
|
||
"type": "user_autocomplete",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"session_secret": {
|
||
"category": "Authentication",
|
||
"description": "Secret key for session encryption (min 32 characters)",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": True, # Required when auth_enabled=True (validated in config.py)
|
||
"restart_required": True,
|
||
},
|
||
"session_lifetime_days": {
|
||
"category": "Authentication",
|
||
"description": "Session lifetime in days (default 30). Determines how long a user stays logged in.",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"session_lifetime_custom_days": {
|
||
"category": "Authentication",
|
||
"description": "Override session_lifetime_days with a custom value. Takes precedence when set.",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"qr_login_challenge_ttl_seconds": {
|
||
"category": "Authentication",
|
||
"description": "Time-to-live in seconds for QR login challenges (default 120).",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"admin_username": {
|
||
"category": "Authentication",
|
||
"description": "Admin username for local authentication",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"admin_password": {
|
||
"category": "Authentication",
|
||
"description": "Admin password for local authentication",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"authentik_client_id": {
|
||
"category": "Authentication",
|
||
"description": "Authentik OAuth2 client ID",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"authentik_client_secret": {
|
||
"category": "Authentication",
|
||
"description": "Authentik OAuth2 client secret",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"authentik_config_url": {
|
||
"category": "Authentication",
|
||
"description": "Authentik OpenID Connect configuration URL",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"oauth_provider_name": {
|
||
"category": "Authentication",
|
||
"description": "Display name for OAuth provider (e.g., 'Authentik', 'Keycloak')",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
# Social Login Providers
|
||
"social_auth_google_enabled": {
|
||
"category": "Social Login",
|
||
"description": (
|
||
"Enable Google Sign-In. Requires SOCIAL_AUTH_GOOGLE_CLIENT_ID and "
|
||
"SOCIAL_AUTH_GOOGLE_CLIENT_SECRET from the Google Cloud Console."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
"help_link": "https://console.cloud.google.com/apis/credentials",
|
||
"help_link_label": "Google Cloud Console",
|
||
},
|
||
"social_auth_google_client_id": {
|
||
"category": "Social Login",
|
||
"description": "Google OAuth2 client ID from the Google Cloud Console.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_google_client_secret": {
|
||
"category": "Social Login",
|
||
"description": "Google OAuth2 client secret from the Google Cloud Console.",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_microsoft_enabled": {
|
||
"category": "Social Login",
|
||
"description": (
|
||
"Enable Microsoft Sign-In (Azure AD / Microsoft Entra ID). Requires "
|
||
"SOCIAL_AUTH_MICROSOFT_CLIENT_ID and SOCIAL_AUTH_MICROSOFT_CLIENT_SECRET "
|
||
"from Azure App Registrations."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
"help_link": "https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade",
|
||
"help_link_label": "Azure Portal",
|
||
},
|
||
"social_auth_microsoft_client_id": {
|
||
"category": "Social Login",
|
||
"description": "Microsoft OAuth2 application (client) ID from Azure App Registrations.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_microsoft_client_secret": {
|
||
"category": "Social Login",
|
||
"description": "Microsoft OAuth2 client secret from Azure App Registrations.",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_microsoft_tenant": {
|
||
"category": "Social Login",
|
||
"description": (
|
||
"Azure AD tenant ID or one of 'common', 'organizations', 'consumers'. "
|
||
"Use 'common' to allow any Microsoft account. Use a specific GUID to "
|
||
"restrict to a single organization."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_apple_enabled": {
|
||
"category": "Social Login",
|
||
"description": (
|
||
"Enable Sign in with Apple. Requires an Apple Developer account with "
|
||
"a Services ID configured for Sign in with Apple."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
"help_link": "https://developer.apple.com/account/resources/identifiers/list/serviceId",
|
||
"help_link_label": "Apple Developer Portal",
|
||
},
|
||
"social_auth_apple_client_id": {
|
||
"category": "Social Login",
|
||
"description": "Apple Services ID (e.g. com.example.docuelevate).",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_apple_team_id": {
|
||
"category": "Social Login",
|
||
"description": "Apple Developer Team ID (10-character alphanumeric string).",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_apple_key_id": {
|
||
"category": "Social Login",
|
||
"description": "Apple Sign-In private key ID from the Apple Developer Portal.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_apple_private_key": {
|
||
"category": "Social Login",
|
||
"description": (
|
||
"Apple Sign-In private key (PEM format). Generate this in the Apple Developer Portal. "
|
||
"Paste the entire key content including BEGIN/END headers."
|
||
),
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_dropbox_use_global_credentials": {
|
||
"category": "Social Login",
|
||
"description": (
|
||
"When True, Dropbox social login uses the global DROPBOX_APP_KEY / DROPBOX_APP_SECRET "
|
||
"credentials instead of requiring separate SOCIAL_AUTH_DROPBOX_CLIENT_ID / "
|
||
"SOCIAL_AUTH_DROPBOX_CLIENT_SECRET values. "
|
||
"Requires SOCIAL_AUTH_DROPBOX_ENABLED=True and global Dropbox credentials to be set."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_dropbox_enabled": {
|
||
"category": "Social Login",
|
||
"description": (
|
||
"Enable Dropbox Sign-In. Uses the same Dropbox App you may already have "
|
||
"configured for storage, or a separate one."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_dropbox_client_id": {
|
||
"category": "Social Login",
|
||
"description": "Dropbox OAuth2 App Key from the Dropbox App Console.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"social_auth_dropbox_client_secret": {
|
||
"category": "Social Login",
|
||
"description": "Dropbox OAuth2 App Secret from the Dropbox App Console.",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
# AI Services
|
||
"openai_api_key": {
|
||
"category": "AI Services",
|
||
"description": "API key for the AI provider (required for OpenAI, Azure, LiteLLM; unused for Ollama)",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"openai_base_url": {
|
||
"category": "AI Services",
|
||
"description": "API base URL – override for Azure endpoints, local proxies, or LiteLLM (default: https://api.openai.com/v1)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"openai_model": {
|
||
"category": "AI Services",
|
||
"description": "Fallback model name used when AI_MODEL is not set (e.g. gpt-4o-mini)",
|
||
"type": "model_picker",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"suggested_models": [
|
||
"gpt-4o",
|
||
"gpt-4o-mini",
|
||
"gpt-4-turbo",
|
||
"gpt-4",
|
||
"gpt-3.5-turbo",
|
||
"o1",
|
||
"o1-mini",
|
||
"o3",
|
||
"o3-mini",
|
||
"gpt-5",
|
||
"gpt-5-nano",
|
||
"claude-3-5-sonnet-20241022",
|
||
"claude-3-5-haiku-20241022",
|
||
"claude-3-opus-20240229",
|
||
"gemini-1.5-pro",
|
||
"gemini-1.5-flash",
|
||
"gemini-2.0-flash-exp",
|
||
"llama3.2",
|
||
"qwen2.5:7b",
|
||
"phi3",
|
||
"mistral",
|
||
],
|
||
},
|
||
"ai_provider": {
|
||
"category": "AI Services",
|
||
"description": "Active AI provider for metadata extraction and OCR refinement",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": ["openai", "azure", "anthropic", "gemini", "ollama", "openrouter", "portkey", "litellm"],
|
||
},
|
||
"ai_model": {
|
||
"category": "AI Services",
|
||
"description": "Model name for the selected provider (overrides OPENAI_MODEL). E.g. gpt-4o, claude-3-5-sonnet-20241022, gemini-1.5-pro, llama3.2",
|
||
"type": "model_picker",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"suggested_models": [
|
||
"gpt-4o",
|
||
"gpt-4o-mini",
|
||
"gpt-4-turbo",
|
||
"gpt-4",
|
||
"gpt-3.5-turbo",
|
||
"o1",
|
||
"o1-mini",
|
||
"o3",
|
||
"o3-mini",
|
||
"gpt-5",
|
||
"gpt-5-nano",
|
||
"claude-3-5-sonnet-20241022",
|
||
"claude-3-5-haiku-20241022",
|
||
"claude-3-opus-20240229",
|
||
"gemini-1.5-pro",
|
||
"gemini-1.5-flash",
|
||
"gemini-2.0-flash-exp",
|
||
"llama3.2",
|
||
"qwen2.5:7b",
|
||
"phi3",
|
||
"mistral",
|
||
],
|
||
},
|
||
"anthropic_api_key": {
|
||
"category": "AI Services",
|
||
"description": "Anthropic API key (required when AI_PROVIDER=anthropic)",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"gemini_api_key": {
|
||
"category": "AI Services",
|
||
"description": "Google AI Studio API key (required when AI_PROVIDER=gemini)",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"ollama_base_url": {
|
||
"category": "AI Services",
|
||
"description": "Ollama server URL for local LLM inference (used when AI_PROVIDER=ollama)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"openrouter_api_key": {
|
||
"category": "AI Services",
|
||
"description": "OpenRouter API key (required when AI_PROVIDER=openrouter)",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"openrouter_base_url": {
|
||
"category": "AI Services",
|
||
"description": "OpenRouter gateway URL (default: https://openrouter.ai/api/v1)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"portkey_api_key": {
|
||
"category": "AI Services",
|
||
"description": "Portkey account API key (required when AI_PROVIDER=portkey)",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"portkey_virtual_key": {
|
||
"category": "AI Services",
|
||
"description": "Portkey Virtual Key – routes to provider credentials stored in the Portkey vault (optional)",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"portkey_config": {
|
||
"category": "AI Services",
|
||
"description": "Portkey Config ID for advanced routing, fallbacks, and load balancing (optional, e.g. pc-my-config-abc123)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"portkey_base_url": {
|
||
"category": "AI Services",
|
||
"description": "Portkey gateway URL (default: https://api.portkey.ai/v1; override for self-hosted deployments)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"azure_openai_api_version": {
|
||
"category": "AI Services",
|
||
"description": "Azure OpenAI API version string (used when AI_PROVIDER=azure, default: 2024-02-01)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Azure Document Intelligence (OCR) – separate from the AI provider above
|
||
"azure_ai_key": {
|
||
"category": "AI Services",
|
||
"description": "Azure Document Intelligence API key for OCR processing",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"azure_region": {
|
||
"category": "AI Services",
|
||
"description": "Azure region for Document Intelligence services (e.g., eastus)",
|
||
"type": "autocomplete",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"azure_endpoint": {
|
||
"category": "AI Services",
|
||
"description": "Azure Document Intelligence endpoint URL",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Document Translation
|
||
"default_document_language": {
|
||
"category": "AI Services",
|
||
"description": (
|
||
"ISO 639-1 language code for the default document translation target "
|
||
"(e.g. 'en', 'de', 'fr'). Documents whose detected language differs "
|
||
"are automatically translated into this language after processing."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# OCR Engine Configuration
|
||
"ocr_providers": {
|
||
"category": "OCR Engines",
|
||
"description": (
|
||
"Active OCR engines. Select one or more engines to enable. "
|
||
"When multiple engines are selected all run in parallel and results are merged. "
|
||
"Stored as a comma-separated list (e.g. azure,tesseract)."
|
||
),
|
||
"type": "multiselect",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": KNOWN_OCR_PROVIDERS,
|
||
},
|
||
"ocr_merge_strategy": {
|
||
"category": "OCR Engines",
|
||
"description": (
|
||
"Strategy for merging results from multiple OCR providers. "
|
||
"ai_merge: use AI to produce best merged text (default); "
|
||
"longest: return the result with most characters; "
|
||
"primary: return only the first provider's result."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": ["ai_merge", "longest", "primary"],
|
||
},
|
||
# OCR – Tesseract
|
||
"tesseract_cmd": {
|
||
"category": "OCR Engines",
|
||
"description": "Path to the Tesseract binary (e.g. /usr/bin/tesseract). Leave blank to use system PATH.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"tesseract_language": {
|
||
"category": "OCR Engines",
|
||
"description": (
|
||
"Tesseract language code(s), e.g. 'eng' or 'eng+deu'. "
|
||
"Combine multiple with '+'. Default: eng+deu (English + German)."
|
||
),
|
||
"type": "autocomplete",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# OCR – EasyOCR
|
||
"easyocr_languages": {
|
||
"category": "OCR Engines",
|
||
"description": ("Comma-separated EasyOCR language codes, e.g. 'en,de,fr'. Default: en,de (English + German)."),
|
||
"type": "autocomplete",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"easyocr_gpu": {
|
||
"category": "OCR Engines",
|
||
"description": "Enable GPU acceleration for EasyOCR (requires CUDA). Default: False.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# OCR – Mistral
|
||
"mistral_api_key": {
|
||
"category": "OCR Engines",
|
||
"description": "Mistral API key (required when 'mistral' is in OCR_PROVIDERS).",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"mistral_ocr_model": {
|
||
"category": "OCR Engines",
|
||
"description": "Mistral OCR model name. Default: mistral-ocr-latest.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# OCR – Google Cloud Document AI
|
||
"google_docai_credentials_json": {
|
||
"category": "OCR Engines",
|
||
"description": (
|
||
"Google Cloud service account credentials JSON for Document AI "
|
||
"(optional; falls back to google_drive_credentials_json or Application Default Credentials)."
|
||
),
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_docai_project_id": {
|
||
"category": "OCR Engines",
|
||
"description": "GCP project ID for Google Cloud Document AI (required when 'google_docai' is in OCR_PROVIDERS).",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_docai_processor_id": {
|
||
"category": "OCR Engines",
|
||
"description": "Document AI processor ID (required when 'google_docai' is in OCR_PROVIDERS).",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_docai_location": {
|
||
"category": "OCR Engines",
|
||
"description": "Document AI processor location (e.g. 'us' or 'eu'). Default: us.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": ["us", "eu"],
|
||
},
|
||
# Storage Providers - Dropbox
|
||
"dropbox_enabled": {
|
||
"category": "Storage Providers",
|
||
"description": "Enable Dropbox as an upload destination. When disabled, no documents will be sent to Dropbox even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dropbox_app_key": {
|
||
"category": "Storage Providers",
|
||
"description": "Dropbox app key for OAuth authentication",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dropbox_app_secret": {
|
||
"category": "Storage Providers",
|
||
"description": "Dropbox app secret for OAuth authentication",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dropbox_folder": {
|
||
"category": "Storage Providers",
|
||
"description": "Dropbox folder path for document storage",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dropbox_refresh_token": {
|
||
"category": "Storage Providers",
|
||
"description": "Dropbox OAuth refresh token",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dropbox_allow_global_credentials_for_integrations": {
|
||
"category": "Storage Providers",
|
||
"description": (
|
||
"When True, users may authorize their personal Dropbox integrations using the global "
|
||
"DROPBOX_APP_KEY / DROPBOX_APP_SECRET credentials configured by the admin, without "
|
||
"needing to create their own Dropbox app."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Storage Providers - Nextcloud
|
||
"nextcloud_enabled": {
|
||
"category": "Storage Providers",
|
||
"description": "Enable Nextcloud as an upload destination. When disabled, no documents will be sent to Nextcloud even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"nextcloud_upload_url": {
|
||
"category": "Storage Providers",
|
||
"description": "Nextcloud WebDAV upload URL",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"nextcloud_username": {
|
||
"category": "Storage Providers",
|
||
"description": "Nextcloud username for authentication",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"nextcloud_password": {
|
||
"category": "Storage Providers",
|
||
"description": "Nextcloud password or app password",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"nextcloud_folder": {
|
||
"category": "Storage Providers",
|
||
"description": "Nextcloud folder path for document storage",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Storage Providers - Paperless-ngx
|
||
"paperless_enabled": {
|
||
"category": "Storage Providers",
|
||
"description": "Enable Paperless-ngx as an upload destination. When disabled, no documents will be sent to Paperless-ngx even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"paperless_ngx_api_token": {
|
||
"category": "Storage Providers",
|
||
"description": "Paperless-ngx API authentication token",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"paperless_host": {
|
||
"category": "Storage Providers",
|
||
"description": "Paperless-ngx host URL (e.g., https://paperless.example.com)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Storage Providers - Google Drive
|
||
"google_drive_enabled": {
|
||
"category": "Storage Providers",
|
||
"description": "Enable Google Drive as an upload destination. When disabled, no documents will be sent to Google Drive even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_drive_credentials_json": {
|
||
"category": "Storage Providers",
|
||
"description": "Google Drive service account credentials JSON",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_drive_folder_id": {
|
||
"category": "Storage Providers",
|
||
"description": "Google Drive folder ID for document storage",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_drive_delegate_to": {
|
||
"category": "Storage Providers",
|
||
"description": "Optional delegated user email for Google Drive service account",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_drive_use_oauth": {
|
||
"category": "Storage Providers",
|
||
"description": "Use OAuth instead of service account for Google Drive",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_drive_client_id": {
|
||
"category": "Storage Providers",
|
||
"description": "Google Drive OAuth client ID",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_drive_client_secret": {
|
||
"category": "Storage Providers",
|
||
"description": "Google Drive OAuth client secret",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_drive_refresh_token": {
|
||
"category": "Storage Providers",
|
||
"description": "Google Drive OAuth refresh token",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Storage Providers - OneDrive
|
||
"onedrive_enabled": {
|
||
"category": "Storage Providers",
|
||
"description": "Enable OneDrive as an upload destination. When disabled, no documents will be sent to OneDrive even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"onedrive_client_id": {
|
||
"category": "Storage Providers",
|
||
"description": "OneDrive OAuth client ID",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"onedrive_client_secret": {
|
||
"category": "Storage Providers",
|
||
"description": "OneDrive OAuth client secret",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"onedrive_tenant_id": {
|
||
"category": "Storage Providers",
|
||
"description": "OneDrive tenant ID (use 'common' for personal accounts)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"onedrive_refresh_token": {
|
||
"category": "Storage Providers",
|
||
"description": "OneDrive OAuth refresh token",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"onedrive_folder_path": {
|
||
"category": "Storage Providers",
|
||
"description": "OneDrive folder path for document storage",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Storage Providers - SharePoint
|
||
"sharepoint_client_id": {
|
||
"category": "Storage Providers",
|
||
"description": "SharePoint Azure AD application (client) ID",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sharepoint_client_secret": {
|
||
"category": "Storage Providers",
|
||
"description": "SharePoint Azure AD client secret",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sharepoint_tenant_id": {
|
||
"category": "Storage Providers",
|
||
"description": "SharePoint Azure AD tenant ID (use 'common' for multi-tenant apps)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sharepoint_refresh_token": {
|
||
"category": "Storage Providers",
|
||
"description": "SharePoint OAuth refresh token",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sharepoint_site_url": {
|
||
"category": "Storage Providers",
|
||
"description": "SharePoint site URL (e.g. https://tenant.sharepoint.com/sites/sitename)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sharepoint_document_library": {
|
||
"category": "Storage Providers",
|
||
"description": "SharePoint document library name (default: 'Documents')",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sharepoint_folder_path": {
|
||
"category": "Storage Providers",
|
||
"description": "Subfolder path inside the SharePoint document library",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Storage Providers - WebDAV
|
||
"webdav_enabled": {
|
||
"category": "Storage Providers",
|
||
"description": "Enable WebDAV as an upload destination. When disabled, no documents will be sent to WebDAV even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"webdav_url": {
|
||
"category": "Storage Providers",
|
||
"description": "WebDAV server URL",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"webdav_username": {
|
||
"category": "Storage Providers",
|
||
"description": "WebDAV username for authentication",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"webdav_password": {
|
||
"category": "Storage Providers",
|
||
"description": "WebDAV password for authentication",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"webdav_folder": {
|
||
"category": "Storage Providers",
|
||
"description": "WebDAV folder path for document storage",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"webdav_verify_ssl": {
|
||
"category": "Storage Providers",
|
||
"description": "Verify SSL certificates for WebDAV connections",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Storage Providers - FTP
|
||
"ftp_enabled": {
|
||
"category": "Storage Providers",
|
||
"description": "Enable FTP as an upload destination. When disabled, no documents will be sent to FTP even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"ftp_host": {
|
||
"category": "Storage Providers",
|
||
"description": "FTP server hostname or IP address",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"ftp_port": {
|
||
"category": "Storage Providers",
|
||
"description": "FTP server port (default: 21)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"ftp_username": {
|
||
"category": "Storage Providers",
|
||
"description": "FTP username for authentication",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"ftp_password": {
|
||
"category": "Storage Providers",
|
||
"description": "FTP password for authentication",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"ftp_folder": {
|
||
"category": "Storage Providers",
|
||
"description": "FTP folder path for document storage",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"ftp_use_tls": {
|
||
"category": "Storage Providers",
|
||
"description": "Use TLS encryption for FTP connections (FTPS)",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"ftp_allow_plaintext": {
|
||
"category": "Storage Providers",
|
||
"description": "Allow fallback to plaintext FTP if TLS fails",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Storage Providers - SFTP
|
||
"sftp_enabled": {
|
||
"category": "Storage Providers",
|
||
"description": "Enable SFTP as an upload destination. When disabled, no documents will be sent to SFTP even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sftp_host": {
|
||
"category": "Storage Providers",
|
||
"description": "SFTP server hostname or IP address",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sftp_port": {
|
||
"category": "Storage Providers",
|
||
"description": "SFTP server port (default: 22)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sftp_username": {
|
||
"category": "Storage Providers",
|
||
"description": "SFTP username for authentication",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sftp_password": {
|
||
"category": "Storage Providers",
|
||
"description": "SFTP password for authentication",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sftp_folder": {
|
||
"category": "Storage Providers",
|
||
"description": "SFTP folder path for document storage",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sftp_private_key": {
|
||
"category": "Storage Providers",
|
||
"description": "SFTP private key for key-based authentication",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sftp_private_key_passphrase": {
|
||
"category": "Storage Providers",
|
||
"description": "Passphrase for encrypted SFTP private key",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sftp_disable_host_key_verification": {
|
||
"category": "Storage Providers",
|
||
"description": "Disable host key verification (not recommended for production)",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Storage Providers - iCloud Drive
|
||
"icloud_enabled": {
|
||
"category": "Storage Providers",
|
||
"description": "Enable iCloud Drive as an upload destination. When disabled, no documents will be sent to iCloud Drive even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"icloud_username": {
|
||
"category": "Storage Providers",
|
||
"description": "Apple ID email address for iCloud Drive authentication",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"icloud_password": {
|
||
"category": "Storage Providers",
|
||
"description": "App-specific password for iCloud Drive (generate at https://appleid.apple.com)",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"icloud_folder": {
|
||
"category": "Storage Providers",
|
||
"description": "Target folder path in iCloud Drive (e.g. Documents/Uploads)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"icloud_cookie_directory": {
|
||
"category": "Storage Providers",
|
||
"description": "Directory for persisting iCloud session cookies (default: ~/.pyicloud)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Storage Providers - AWS S3
|
||
"s3_enabled": {
|
||
"category": "Storage Providers",
|
||
"description": "Enable Amazon S3 as an upload destination. When disabled, no documents will be sent to S3 even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"aws_access_key_id": {
|
||
"category": "Storage Providers",
|
||
"description": "AWS access key ID for S3",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"aws_secret_access_key": {
|
||
"category": "Storage Providers",
|
||
"description": "AWS secret access key for S3",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"aws_region": {
|
||
"category": "Storage Providers",
|
||
"description": "AWS region for S3 bucket (default: us-east-1)",
|
||
"type": "autocomplete",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"s3_bucket_name": {
|
||
"category": "Storage Providers",
|
||
"description": "S3 bucket name for document storage",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"s3_folder_prefix": {
|
||
"category": "Storage Providers",
|
||
"description": "Optional folder prefix in S3 bucket (e.g., 'uploads/')",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"s3_storage_class": {
|
||
"category": "Storage Providers",
|
||
"description": "S3 storage class (e.g., STANDARD, INTELLIGENT_TIERING)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": [
|
||
"STANDARD",
|
||
"REDUCED_REDUNDANCY",
|
||
"STANDARD_IA",
|
||
"ONEZONE_IA",
|
||
"INTELLIGENT_TIERING",
|
||
"GLACIER",
|
||
"DEEP_ARCHIVE",
|
||
"GLACIER_IR",
|
||
],
|
||
},
|
||
"s3_acl": {
|
||
"category": "Storage Providers",
|
||
"description": "S3 object ACL (e.g., private, public-read)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": [
|
||
"private",
|
||
"public-read",
|
||
"public-read-write",
|
||
"authenticated-read",
|
||
"aws-exec-read",
|
||
"bucket-owner-read",
|
||
"bucket-owner-full-control",
|
||
],
|
||
},
|
||
# Email Settings
|
||
"email_host": {
|
||
"category": "Email",
|
||
"description": "SMTP server hostname (shared – used for password reset and verification emails)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"email_port": {
|
||
"category": "Email",
|
||
"description": "SMTP server port (default: 587)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"email_username": {
|
||
"category": "Email",
|
||
"description": "SMTP username for authentication",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"email_password": {
|
||
"category": "Email",
|
||
"description": "SMTP password for authentication",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"email_use_tls": {
|
||
"category": "Email",
|
||
"description": "Use TLS encryption for SMTP",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"email_sender": {
|
||
"category": "Email",
|
||
"description": "From address for outgoing emails (defaults to email_username)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"email_default_recipient": {
|
||
"category": "Email",
|
||
"description": "Default recipient email address (shared – used for system notifications)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Email Destination Settings (dedicated SMTP for document delivery)
|
||
"dest_email_enabled": {
|
||
"category": "Email Destination",
|
||
"description": "Enable Email as an upload destination. When disabled, no documents will be delivered via email even if credentials are configured.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dest_email_host": {
|
||
"category": "Email Destination",
|
||
"description": "SMTP server hostname for document delivery (separate from shared email settings)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dest_email_port": {
|
||
"category": "Email Destination",
|
||
"description": "SMTP port for document delivery (default: 587)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dest_email_username": {
|
||
"category": "Email Destination",
|
||
"description": "SMTP username for document delivery",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dest_email_password": {
|
||
"category": "Email Destination",
|
||
"description": "SMTP password for document delivery",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dest_email_use_tls": {
|
||
"category": "Email Destination",
|
||
"description": "Use TLS encryption for document delivery SMTP",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dest_email_sender": {
|
||
"category": "Email Destination",
|
||
"description": "From address for document delivery emails (defaults to dest_email_username)",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dest_email_default_recipient": {
|
||
"category": "Email Destination",
|
||
"description": "Default recipient email for document delivery when none is specified",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Watch Folder / Ingest Settings
|
||
"watch_folders": {
|
||
"category": "Watch Folders",
|
||
"description": (
|
||
"Comma-separated list of absolute local filesystem paths that DocuElevate will "
|
||
"poll for new files to ingest. Works with any mounted path (SMB/CIFS, NFS, etc.). "
|
||
"Example: /watchfolders/scanner,/mnt/shared/inbox"
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"watch_folder_poll_interval": {
|
||
"category": "Watch Folders",
|
||
"description": "Poll interval in minutes for local watch folder scanning (default: 1)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"watch_folder_delete_after_process": {
|
||
"category": "Watch Folders",
|
||
"description": (
|
||
"Delete files from local watch folders after they have been enqueued for processing. "
|
||
"When False (default), processed files are tracked via cache to avoid re-ingestion."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# FTP Ingest
|
||
"ftp_ingest_enabled": {
|
||
"category": "Watch Folders",
|
||
"description": "Enable FTP watch folder ingestion (requires FTP_INGEST_FOLDER and FTP connection settings)",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"ftp_ingest_folder": {
|
||
"category": "Watch Folders",
|
||
"description": (
|
||
"FTP folder path to poll for new documents to ingest. "
|
||
"Uses the existing FTP connection settings (FTP_HOST, FTP_USERNAME, FTP_PASSWORD)."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"ftp_ingest_delete_after_process": {
|
||
"category": "Watch Folders",
|
||
"description": "Delete files from the FTP ingest folder after they have been downloaded and enqueued",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# SFTP Ingest
|
||
"sftp_ingest_enabled": {
|
||
"category": "Watch Folders",
|
||
"description": "Enable SFTP watch folder ingestion (requires SFTP_INGEST_FOLDER and SFTP connection settings)",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sftp_ingest_folder": {
|
||
"category": "Watch Folders",
|
||
"description": (
|
||
"SFTP folder path to poll for new documents to ingest. "
|
||
"Uses the existing SFTP connection settings (SFTP_HOST, SFTP_USERNAME, SFTP_PASSWORD/SFTP_PRIVATE_KEY)."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"sftp_ingest_delete_after_process": {
|
||
"category": "Watch Folders",
|
||
"description": "Delete files from the SFTP ingest folder after they have been downloaded and enqueued",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Cloud Provider Watch Folders — Dropbox
|
||
"dropbox_ingest_enabled": {
|
||
"category": "Watch Folders",
|
||
"description": "Enable Dropbox watch folder ingestion (requires Dropbox OAuth credentials)",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dropbox_ingest_folder": {
|
||
"category": "Watch Folders",
|
||
"description": "Dropbox folder path to poll for new documents (e.g. /Inbox/Scanner). Uses existing Dropbox credentials.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"dropbox_ingest_delete_after_process": {
|
||
"category": "Watch Folders",
|
||
"description": "Delete files from Dropbox ingest folder after download and enqueue",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Cloud Provider Watch Folders — Google Drive
|
||
"google_drive_ingest_enabled": {
|
||
"category": "Watch Folders",
|
||
"description": "Enable Google Drive watch folder ingestion (requires Google Drive credentials)",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_drive_ingest_folder_id": {
|
||
"category": "Watch Folders",
|
||
"description": "Google Drive folder ID to poll for new documents. Uses existing Google Drive credentials.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_drive_ingest_delete_after_process": {
|
||
"category": "Watch Folders",
|
||
"description": "Delete files from Google Drive ingest folder after download and enqueue",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Cloud Provider Watch Folders — OneDrive
|
||
"onedrive_ingest_enabled": {
|
||
"category": "Watch Folders",
|
||
"description": "Enable OneDrive watch folder ingestion (requires OneDrive MSAL credentials)",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"onedrive_ingest_folder_path": {
|
||
"category": "Watch Folders",
|
||
"description": "OneDrive folder path to poll for new documents (e.g. /Inbox/Scanner). Uses existing OneDrive credentials.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"onedrive_ingest_delete_after_process": {
|
||
"category": "Watch Folders",
|
||
"description": "Delete files from OneDrive ingest folder after download and enqueue",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Cloud Provider Watch Folders — Nextcloud
|
||
"nextcloud_ingest_enabled": {
|
||
"category": "Watch Folders",
|
||
"description": "Enable Nextcloud watch folder ingestion (requires Nextcloud WebDAV credentials)",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"nextcloud_ingest_folder": {
|
||
"category": "Watch Folders",
|
||
"description": "Nextcloud folder path to poll for new documents (e.g. /Scans/Inbox). Uses existing Nextcloud credentials.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"nextcloud_ingest_delete_after_process": {
|
||
"category": "Watch Folders",
|
||
"description": "Delete files from Nextcloud ingest folder after download and enqueue",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Cloud Provider Watch Folders — S3
|
||
"s3_ingest_enabled": {
|
||
"category": "Watch Folders",
|
||
"description": "Enable Amazon S3 prefix (watch folder) ingestion (requires S3/AWS credentials)",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"s3_ingest_prefix": {
|
||
"category": "Watch Folders",
|
||
"description": "S3 key prefix to poll for new objects to ingest (e.g. inbox/scanner/). Uses existing S3 credentials.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"s3_ingest_delete_after_process": {
|
||
"category": "Watch Folders",
|
||
"description": "Delete objects from S3 ingest prefix after download and enqueue",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Cloud Provider Watch Folders — WebDAV
|
||
"webdav_ingest_enabled": {
|
||
"category": "Watch Folders",
|
||
"description": "Enable WebDAV watch folder ingestion (requires WebDAV URL and credentials)",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"webdav_ingest_folder": {
|
||
"category": "Watch Folders",
|
||
"description": "WebDAV folder path to poll for new documents. Uses existing WebDAV URL and credentials.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"webdav_ingest_delete_after_process": {
|
||
"category": "Watch Folders",
|
||
"description": "Delete files from WebDAV ingest folder after download and enqueue",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# IMAP Settings - Account 1
|
||
"imap1_host": {
|
||
"category": "IMAP",
|
||
"description": "IMAP server hostname for account 1",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap1_port": {
|
||
"category": "IMAP",
|
||
"description": "IMAP server port for account 1 (default: 993)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap1_username": {
|
||
"category": "IMAP",
|
||
"description": "IMAP username for account 1",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap1_password": {
|
||
"category": "IMAP",
|
||
"description": "IMAP password for account 1",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap1_ssl": {
|
||
"category": "IMAP",
|
||
"description": "Use SSL for IMAP account 1",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap1_poll_interval_minutes": {
|
||
"category": "IMAP",
|
||
"description": "Poll interval in minutes for IMAP account 1 (default: 5)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap1_delete_after_process": {
|
||
"category": "IMAP",
|
||
"description": "Delete emails after processing for IMAP account 1",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# IMAP Settings - Account 2
|
||
"imap2_host": {
|
||
"category": "IMAP",
|
||
"description": "IMAP server hostname for account 2",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap2_port": {
|
||
"category": "IMAP",
|
||
"description": "IMAP server port for account 2 (default: 993)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap2_username": {
|
||
"category": "IMAP",
|
||
"description": "IMAP username for account 2",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap2_password": {
|
||
"category": "IMAP",
|
||
"description": "IMAP password for account 2",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap2_ssl": {
|
||
"category": "IMAP",
|
||
"description": "Use SSL for IMAP account 2",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap2_poll_interval_minutes": {
|
||
"category": "IMAP",
|
||
"description": "Poll interval in minutes for IMAP account 2 (default: 10)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap2_delete_after_process": {
|
||
"category": "IMAP",
|
||
"description": "Delete emails after processing for IMAP account 2",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap_readonly_mode": {
|
||
"category": "IMAP",
|
||
"description": (
|
||
"When enabled, IMAP processing fetches and processes attachments but does NOT modify "
|
||
"the mailbox (no starring, labeling, deleting, or flag changes). "
|
||
"Use for preprod instances sharing a mailbox with production."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"imap_attachment_filter": {
|
||
"category": "IMAP",
|
||
"description": (
|
||
"Controls which attachment types are ingested from IMAP emails. "
|
||
"Accepted values: 'documents_only' (PDFs and office files only, default) or 'all' (including images). "
|
||
"Per-user IMAP accounts can override this global default."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Monitoring - Uptime Kuma
|
||
"uptime_kuma_url": {
|
||
"category": "Monitoring",
|
||
"description": "Uptime Kuma push monitor URL",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"uptime_kuma_ping_interval": {
|
||
"category": "Monitoring",
|
||
"description": "Uptime Kuma ping interval in minutes (default: 5)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Processing Settings
|
||
"http_request_timeout": {
|
||
"category": "Processing",
|
||
"description": "Timeout for HTTP requests in seconds (default: 120)",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"processall_throttle_threshold": {
|
||
"category": "Processing",
|
||
"description": "Number of files above which throttling is applied in /processall",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"processall_throttle_delay": {
|
||
"category": "Processing",
|
||
"description": "Delay in seconds between task submissions when throttling in /processall",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"upload_concurrency": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Maximum number of files uploaded simultaneously from the browser. "
|
||
"Limits parallel uploads to prevent API overload when dragging directories. Default: 3."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"upload_queue_delay_ms": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Delay in milliseconds between starting each upload slot when queue is active. "
|
||
"Staggers upload starts to smooth out server load. Default: 500 ms."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"enable_text_quality_check": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Enable AI-based quality check for embedded PDF text. "
|
||
"When enabled, text extracted from non-digital PDFs is evaluated by the AI model. "
|
||
"Poor-quality text (OCR artefacts, typos, incoherence) triggers automatic re-OCR. "
|
||
"Digitally-created PDFs (Word, LibreOffice, LaTeX, etc.) are always trusted and skip the check."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"text_quality_threshold": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Minimum quality score (0–100) required to accept embedded PDF text without re-OCR. "
|
||
"Text scoring below this threshold triggers a fresh OCR pass. "
|
||
"Default: 85. Lower values are more permissive; higher values enforce stricter quality."
|
||
),
|
||
"type": "slider",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"min": 0,
|
||
"max": 100,
|
||
"step": 1,
|
||
},
|
||
"text_quality_significant_issues": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Comma-separated list of quality issue labels that force OCR re-run even when the quality "
|
||
"score meets TEXT_QUALITY_THRESHOLD. Any matching issue in the AI assessment will trigger "
|
||
"re-OCR regardless of the numeric score. "
|
||
"Default: excessive_typos,garbage_characters,incoherent_text,fragmented_sentences"
|
||
),
|
||
"type": "list",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Notifications Settings
|
||
"notification_urls": {
|
||
"category": "Notifications",
|
||
"description": "Comma-separated list of Apprise notification URLs (e.g., discord://, telegram://)",
|
||
"type": "list",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"notify_on_task_failure": {
|
||
"category": "Notifications",
|
||
"description": "Send notifications when Celery tasks fail",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"notify_on_credential_failure": {
|
||
"category": "Notifications",
|
||
"description": "Send notifications when credential checks fail",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"notify_on_startup": {
|
||
"category": "Notifications",
|
||
"description": "Send notifications when application starts",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"notify_on_shutdown": {
|
||
"category": "Notifications",
|
||
"description": "Send notifications when application shuts down",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"notify_on_file_processed": {
|
||
"category": "Notifications",
|
||
"description": "Send notifications when files are successfully processed",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"notify_on_user_signup": {
|
||
"category": "Notifications",
|
||
"description": "Send admin notifications when a new user signs up",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"notify_on_plan_change": {
|
||
"category": "Notifications",
|
||
"description": "Send admin notifications when a user changes their subscription plan",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"notify_on_payment_issue": {
|
||
"category": "Notifications",
|
||
"description": "Send admin notifications when a payment issue is reported for a user",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Feature Flags
|
||
"allow_file_delete": {
|
||
"category": "Feature Flags",
|
||
"description": "Allow deleting files from the database",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"enable_search": {
|
||
"category": "Feature Flags",
|
||
"description": "Enable Meilisearch full-text search integration. Default: True.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"webhook_enabled": {
|
||
"category": "Feature Flags",
|
||
"description": "Enable webhook delivery for document events. Default: True.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"automation_hooks_enabled": {
|
||
"category": "Feature Flags",
|
||
"description": (
|
||
"Enable Zapier / Make.com automation hook subscriptions and delivery. "
|
||
"When enabled, external automation platforms can subscribe to DocuElevate events "
|
||
"via the REST hooks protocol. Default: True."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"compliance_enabled": {
|
||
"category": "Feature Flags",
|
||
"description": (
|
||
"Enable the compliance templates dashboard (GDPR, HIPAA, SOC 2). "
|
||
"When enabled, admins can view compliance status and apply "
|
||
"pre-built regulatory configurations. Default: True."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"factory_reset_on_startup": {
|
||
"category": "Feature Flags",
|
||
"description": (
|
||
"Wipe all user data on every startup so the instance always starts fresh. "
|
||
"Useful for demo/testing environments. Default: False."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"enable_factory_reset": {
|
||
"category": "Feature Flags",
|
||
"description": (
|
||
"Show the System Reset page in the admin UI. Allows administrators to "
|
||
"trigger a full data wipe or a wipe-and-reimport from the web interface. Default: False."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Backup / Restore
|
||
"backup_enabled": {
|
||
"category": "Backup",
|
||
"description": ("Enable automatic scheduled database backups (hourly, daily, weekly). Default: True."),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"backup_dir": {
|
||
"category": "Backup",
|
||
"description": (
|
||
"Directory where local backup archives are stored. Defaults to <workdir>/backups when not set."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"backup_remote_destination": {
|
||
"category": "Backup",
|
||
"description": (
|
||
"Storage provider for remote backup copies. "
|
||
"Accepted values: s3, dropbox, google_drive, onedrive, sharepoint, nextcloud, webdav, ftp, sftp, email. "
|
||
"Leave empty to keep backups local only."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": [
|
||
"",
|
||
"s3",
|
||
"dropbox",
|
||
"google_drive",
|
||
"onedrive",
|
||
"sharepoint",
|
||
"nextcloud",
|
||
"webdav",
|
||
"ftp",
|
||
"sftp",
|
||
"email",
|
||
],
|
||
},
|
||
"backup_remote_folder": {
|
||
"category": "Backup",
|
||
"description": (
|
||
"Sub-folder / key prefix used when uploading backup archives to the remote destination. Default: 'backups'."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"backup_retain_hourly": {
|
||
"category": "Backup",
|
||
"description": "Number of hourly backups to retain (default 96 = 4 days × 24 h).",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"backup_retain_daily": {
|
||
"category": "Backup",
|
||
"description": "Number of daily backups to retain (default 21 = 3 weeks).",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"backup_retain_weekly": {
|
||
"category": "Backup",
|
||
"description": "Number of weekly backups to retain (default 13 ≈ 3 months / 91 days).",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# UI / Appearance
|
||
"ui_default_color_scheme": {
|
||
"category": "UI",
|
||
"description": (
|
||
"Default color scheme for the web interface. "
|
||
"Options: 'system' (follow OS preference), 'light', 'dark'. "
|
||
"Individual users can override this with the in-app toggle; their choice is persisted in localStorage."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": ["system", "light", "dark"],
|
||
},
|
||
# Authentication – additional
|
||
"admin_group_name": {
|
||
"category": "Authentication",
|
||
"description": "Name of the admin group in the OAuth provider (e.g. Authentik). Default: admin.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
# Storage Providers – S3 (additional)
|
||
# (s3_bucket_name, s3_folder_prefix, s3_storage_class, s3_acl already in Storage Providers above)
|
||
# Paperless – additional
|
||
"paperless_custom_field_absender": {
|
||
"category": "Storage Providers",
|
||
"description": "Name of the 'absender' (sender) custom field in Paperless-ngx.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"paperless_custom_fields_mapping": {
|
||
"category": "Storage Providers",
|
||
"description": (
|
||
"JSON mapping of metadata field names to Paperless custom field names. "
|
||
'Example: {"absender": "Sender", "empfaenger": "Recipient", "language": "Language"}.'
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Meilisearch / Search
|
||
"meilisearch_url": {
|
||
"category": "Core",
|
||
"description": "URL of the Meilisearch search engine instance. Default: http://meilisearch:7700.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"meilisearch_api_key": {
|
||
"category": "Core",
|
||
"description": "Meilisearch API key (master key or search-only key). Optional for local development.",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"meilisearch_index_name": {
|
||
"category": "Core",
|
||
"description": "Name of the Meilisearch index for documents. Default: documents.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
# PDF/A Archival Conversion
|
||
"enable_pdfa_conversion": {
|
||
"category": "PDF/A Archival",
|
||
"description": (
|
||
"Master switch: enable PDF/A archival variant generation. "
|
||
"When enabled, PDF/A copies of both the original and processed files are created. "
|
||
"Uses ocrmypdf with Ghostscript for the conversion. Default: False."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"pdfa_format": {
|
||
"category": "PDF/A Archival",
|
||
"description": "PDF/A format variant to produce: 1 = PDF/A-1b, 2 = PDF/A-2b, 3 = PDF/A-3b. Default: 2.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": ["1", "2", "3"],
|
||
},
|
||
"pdfa_upload_original": {
|
||
"category": "PDF/A Archival",
|
||
"description": (
|
||
"Upload the original-file PDF/A variant to all configured storage providers. "
|
||
"Files are placed in the provider's folder + PDFA_UPLOAD_FOLDER subfolder. Default: False."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"pdfa_upload_processed": {
|
||
"category": "PDF/A Archival",
|
||
"description": (
|
||
"Upload the processed-file PDF/A variant to all configured storage providers. "
|
||
"Files are placed in the provider's folder + PDFA_UPLOAD_FOLDER subfolder. Default: False."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"pdfa_upload_folder": {
|
||
"category": "PDF/A Archival",
|
||
"description": (
|
||
"Subfolder name appended to each storage provider's configured folder for PDF/A uploads. "
|
||
"For example if Dropbox folder is '/Documents' and this is 'pdfa', files go to '/Documents/pdfa'. "
|
||
"Set to empty string to upload into the same folder. Default: pdfa."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"google_drive_pdfa_folder_id": {
|
||
"category": "PDF/A Archival",
|
||
"description": (
|
||
"Google Drive folder ID for PDF/A uploads (Drive uses IDs not paths). "
|
||
"If empty, uses the standard google_drive_folder_id."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"pdfa_timestamp_enabled": {
|
||
"category": "PDF/A Archival",
|
||
"description": (
|
||
"Enable RFC 3161 timestamping of PDF/A files via a Timestamp Authority (TSA). "
|
||
"Creates a .tsr file alongside each PDF/A file for legal proof of existence. "
|
||
"Requires openssl binary on PATH. Default: False."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"pdfa_timestamp_url": {
|
||
"category": "PDF/A Archival",
|
||
"description": (
|
||
"URL of the RFC 3161 Timestamp Authority (TSA). "
|
||
"Default: FreeTSA (https://freetsa.org/tsr). Other options: GlobalSign, DigiStamp, "
|
||
"or any RFC 3161-compliant TSA."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Deduplication
|
||
"enable_deduplication": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Enable deduplication check before processing. Files with the same SHA-256 hash "
|
||
"as previously processed files will be skipped. Default: True."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"show_deduplication_step": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Show the 'Check for Duplicates' step in processing history. "
|
||
"If False, the check is still performed but not displayed. Default: True."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"near_duplicate_threshold": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Minimum cosine similarity score (0.0–1.0) between two documents' text embeddings "
|
||
"to consider them near-duplicates. Higher values require closer content matches. Default: 0.85."
|
||
),
|
||
"type": "slider",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"min": 0,
|
||
"max": 1,
|
||
"step": 0.01,
|
||
},
|
||
# Embeddings
|
||
"embedding_model": {
|
||
"category": "AI Services",
|
||
"description": (
|
||
"Model name used for generating text embeddings via the OpenAI-compatible API. "
|
||
"Embeddings drive the document similarity feature. Default: text-embedding-3-small."
|
||
),
|
||
"type": "autocomplete",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"embedding_max_tokens": {
|
||
"category": "AI Services",
|
||
"description": (
|
||
"Maximum number of tokens to send to the embedding model. "
|
||
"Text is truncated to approximately this many tokens before calling the API. Default: 8000."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"embedding_backfill_batch_size": {
|
||
"category": "AI Services",
|
||
"description": (
|
||
"Maximum number of files to queue for embedding computation per backfill run. "
|
||
"Keeps the worker and embedding API load bounded. Default: 50."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# File upload size limits
|
||
"max_upload_size": {
|
||
"category": "Core",
|
||
"description": (
|
||
"Maximum file upload size in bytes. Prevents resource exhaustion attacks. Default: 1073741824 (1 GB)."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"max_single_file_size": {
|
||
"category": "Core",
|
||
"description": (
|
||
"Maximum size for a single file chunk in bytes. If set and file exceeds this, "
|
||
"it will be split into smaller chunks for processing. Default: None (no splitting)."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"max_request_body_size": {
|
||
"category": "Core",
|
||
"description": (
|
||
"Maximum request body size in bytes for non-file-upload requests. "
|
||
"Prevents memory exhaustion via oversized JSON/form payloads. Default: 1048576 (1 MB)."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
# Task Retry Settings
|
||
"task_retry_max_retries": {
|
||
"category": "Processing",
|
||
"description": "Maximum number of automatic retry attempts for failed Celery tasks. Default: 3.",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"task_retry_delays": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Comma-separated list of retry countdown values in seconds. "
|
||
"Each value is the delay before the corresponding retry attempt. "
|
||
"Default: 60,300,900 (1 min, 5 min, 15 min)."
|
||
),
|
||
"type": "list",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"task_retry_jitter": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Apply ±20% random jitter to retry countdowns to prevent thundering-herd problems. Default: True."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"step_timeout": {
|
||
"category": "Processing",
|
||
"description": (
|
||
"Timeout in seconds for processing steps. Steps stuck in 'in_progress' longer "
|
||
"than this are marked as failed. Default: 600 (10 minutes)."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Security Headers
|
||
"security_headers_enabled": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Enable security headers middleware. Set to True if deploying without a reverse proxy. "
|
||
"Default: False (most deployments use Traefik/Nginx which add headers)."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"security_header_hsts_enabled": {
|
||
"category": "Security",
|
||
"description": "Enable HTTP Strict Transport Security (HSTS) header. Only effective over HTTPS. Default: True.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"security_header_hsts_value": {
|
||
"category": "Security",
|
||
"description": "HSTS header value. Default: max-age=31536000; includeSubDomains.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"security_header_csp_enabled": {
|
||
"category": "Security",
|
||
"description": "Enable Content-Security-Policy (CSP) header. Default: True.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"security_header_csp_value": {
|
||
"category": "Security",
|
||
"description": (
|
||
"CSP header value. Customize based on your application's resource loading needs. "
|
||
"Default: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; "
|
||
"img-src 'self' data: https:; font-src 'self' data:;"
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"security_header_x_frame_options_enabled": {
|
||
"category": "Security",
|
||
"description": "Enable X-Frame-Options header to prevent clickjacking. Default: True.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"security_header_x_frame_options_value": {
|
||
"category": "Security",
|
||
"description": "X-Frame-Options header value.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
"options": ["DENY", "SAMEORIGIN"],
|
||
},
|
||
"security_header_x_content_type_options_enabled": {
|
||
"category": "Security",
|
||
"description": "Enable X-Content-Type-Options header (always set to 'nosniff'). Default: True.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
# Audit Logging
|
||
"audit_logging_enabled": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Enable audit/request logging middleware. Logs every HTTP request with method, path, "
|
||
"status code, response time, and username. Sensitive query-parameter values are automatically masked. "
|
||
"Default: True."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"audit_log_include_client_ip": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Include the client IP address in audit log entries. "
|
||
"Disable for privacy-sensitive deployments where IP logging is restricted. Default: True."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"audit_siem_enabled": {
|
||
"category": "Security",
|
||
"description": "Enable forwarding of audit events to an external SIEM system (Syslog, Splunk, Logstash, etc.).",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"audit_siem_transport": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Transport used to forward audit events. 'syslog' sends RFC 5424 messages over UDP/TCP. "
|
||
"'http' sends JSON POST payloads to a webhook URL (Splunk HEC, Logstash, Grafana Loki, etc.)."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": ["syslog", "http"],
|
||
},
|
||
"audit_siem_syslog_host": {
|
||
"category": "Security",
|
||
"description": "Hostname or IP of the syslog receiver.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"audit_siem_syslog_port": {
|
||
"category": "Security",
|
||
"description": "Port of the syslog receiver. Default: 514.",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"audit_siem_syslog_protocol": {
|
||
"category": "Security",
|
||
"description": "Protocol for syslog transport: 'udp' or 'tcp'. Default: udp.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
"options": ["udp", "tcp"],
|
||
},
|
||
"audit_siem_http_url": {
|
||
"category": "Security",
|
||
"description": (
|
||
"HTTP endpoint URL for SIEM webhook delivery. Supports Splunk HEC, "
|
||
"Logstash HTTP input, Grafana Loki push API, or any JSON-accepting endpoint."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"audit_siem_http_token": {
|
||
"category": "Security",
|
||
"description": "Bearer / HEC token included in the Authorization header of SIEM HTTP requests.",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"audit_siem_http_custom_headers": {
|
||
"category": "Security",
|
||
"description": "Comma-separated 'Key:Value' pairs of extra headers for SIEM HTTP requests.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Per-user upload rate limiting
|
||
"upload_rate_limit_per_user": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Maximum number of uploads a single user may submit within upload_rate_limit_window seconds. "
|
||
"The health-aware limiter may reduce this dynamically under high Redis queue depth or CPU load. "
|
||
"Default: 20."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"upload_rate_limit_window": {
|
||
"category": "Security",
|
||
"description": ("Sliding window in seconds over which upload_rate_limit_per_user is enforced. Default: 60."),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Rate Limiting
|
||
"rate_limiting_enabled": {
|
||
"category": "Security",
|
||
"description": "Enable rate limiting middleware. Recommended for production to prevent abuse. Default: True.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"rate_limit_default": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Default rate limit for all endpoints (format: 'count/period', e.g. '100/minute'). Default: 100/minute."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"rate_limit_upload": {
|
||
"category": "Security",
|
||
"description": "Rate limit for file upload endpoints to prevent resource exhaustion. Default: 600/minute.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"rate_limit_auth": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Stricter rate limit for authentication endpoints to prevent brute force attacks. Default: 10/minute."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
# CORS
|
||
"cors_enabled": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Enable CORS middleware. Set to False if reverse proxy (Traefik, Nginx) handles CORS headers. "
|
||
"Default: False."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"cors_allowed_origins": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Comma-separated list of allowed CORS origins. Use '*' to allow all origins "
|
||
"(not recommended with cors_allow_credentials=True). "
|
||
"Default: * (all origins)."
|
||
),
|
||
"type": "list",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"cors_allow_credentials": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Allow credentials (cookies, Authorization headers) in CORS requests. "
|
||
"Cannot be True when cors_allowed_origins is '*'. Default: False."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"cors_allowed_methods": {
|
||
"category": "Security",
|
||
"description": (
|
||
"Comma-separated list of allowed HTTP methods for CORS requests. "
|
||
"Default: GET,POST,PUT,DELETE,OPTIONS,PATCH."
|
||
),
|
||
"type": "list",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"cors_allowed_headers": {
|
||
"category": "Security",
|
||
"description": "Comma-separated list of allowed request headers for CORS. Use '*' to allow all. Default: *.",
|
||
"type": "list",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"subscription_overage_percent": {
|
||
"category": "Subscriptions",
|
||
"description": (
|
||
"Soft-limit overage buffer in percent (0–200). The announced monthly quota is increased by this "
|
||
"percentage for actual enforcement. For example, 20 means a 150-doc/month plan enforces at 180 docs "
|
||
"(150 × 1.20). Set to 0 to enforce exactly at the announced limit. Per-plan overage_percent configured "
|
||
"in the Plan Designer overrides this global default. Default: 20."
|
||
),
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Local User Signup
|
||
"allow_local_signup": {
|
||
"category": "Authentication",
|
||
"description": (
|
||
"Allow users to self-register with email and password. "
|
||
"Has no effect unless MULTI_USER_ENABLED is also True. "
|
||
"Requires SMTP (EMAIL_HOST) to be configured so verification emails can be sent."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Stripe Billing
|
||
"stripe_secret_key": {
|
||
"category": "Billing",
|
||
"description": "Stripe secret API key (starts with sk_). Required for payment processing.",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"stripe_publishable_key": {
|
||
"category": "Billing",
|
||
"description": "Stripe publishable key (starts with pk_). Exposed to the browser for Checkout.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"stripe_webhook_secret": {
|
||
"category": "Billing",
|
||
"description": "Stripe webhook signing secret (starts with whsec_). Used to verify incoming webhook payloads.",
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"stripe_success_url": {
|
||
"category": "Billing",
|
||
"description": "Absolute URL Stripe redirects to after a successful checkout (e.g. https://app.example.com/billing/success).",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"stripe_cancel_url": {
|
||
"category": "Billing",
|
||
"description": "Absolute URL Stripe redirects to when a user cancels the checkout flow (e.g. https://app.example.com/pricing).",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Support / Help Center – Zammad integration
|
||
"zammad_url": {
|
||
"category": "Support",
|
||
"description": "Base URL of your Zammad instance (e.g. https://zammad.example.com). Required for the chat widget and feedback form.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"zammad_chat_enabled": {
|
||
"category": "Support",
|
||
"description": "Show a Zammad live-chat widget on the Help Center page.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"zammad_chat_id": {
|
||
"category": "Support",
|
||
"description": "Zammad chat topic ID (see Channels → Chat → Topics in Zammad admin). Default: 1.",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"zammad_form_enabled": {
|
||
"category": "Support",
|
||
"description": "Show a 'Submit a Ticket' feedback form on the Help Center page.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
"support_email": {
|
||
"category": "Support",
|
||
"description": "Support e-mail address displayed on the Help Center page.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
# Logging
|
||
"log_level": {
|
||
"category": "Observability",
|
||
"description": (
|
||
"Python logging level for the application root logger. "
|
||
"Accepts: DEBUG, INFO, WARNING, ERROR, CRITICAL. "
|
||
"When DEBUG=True and LOG_LEVEL is not explicitly set, "
|
||
"the effective level is automatically lowered to DEBUG."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"log_format": {
|
||
"category": "Observability",
|
||
"description": (
|
||
"Log output format: 'text' (human-readable, default) or "
|
||
"'json' (structured JSON lines for SIEM / log aggregation)."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"log_syslog_enabled": {
|
||
"category": "Observability",
|
||
"description": "Forward application logs to a syslog receiver in addition to stdout.",
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"log_syslog_host": {
|
||
"category": "Observability",
|
||
"description": "Hostname or IP of the syslog receiver for application logs.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"log_syslog_port": {
|
||
"category": "Observability",
|
||
"description": "Port of the syslog receiver for application logs.",
|
||
"type": "integer",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"log_syslog_protocol": {
|
||
"category": "Observability",
|
||
"description": "Protocol for syslog transport: 'udp' or 'tcp'.",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
# Observability – Sentry
|
||
"sentry_dsn": {
|
||
"category": "Observability",
|
||
"description": (
|
||
"Sentry DSN (Data Source Name) URL. When set, runtime errors and "
|
||
"performance traces are automatically sent to Sentry. "
|
||
"Leave blank to disable Sentry entirely."
|
||
),
|
||
"type": "string",
|
||
"sensitive": True,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"sentry_environment": {
|
||
"category": "Observability",
|
||
"description": (
|
||
"Environment label attached to every Sentry event "
|
||
"(e.g. 'development', 'staging', 'production'). "
|
||
"Helps you filter events in the Sentry dashboard. Default: 'production'."
|
||
),
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"sentry_traces_sample_rate": {
|
||
"category": "Observability",
|
||
"description": (
|
||
"Fraction of transactions captured for Sentry performance monitoring (0.0–1.0). "
|
||
"0.0 disables tracing; 1.0 captures every request. Default: 0.1 (10%)."
|
||
),
|
||
"type": "float",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"sentry_profiles_sample_rate": {
|
||
"category": "Observability",
|
||
"description": (
|
||
"Fraction of profiled transactions sent to Sentry (0.0–1.0). "
|
||
"Only active when sentry_traces_sample_rate > 0. Default: 0.0 (disabled)."
|
||
),
|
||
"type": "float",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
"sentry_send_default_pii": {
|
||
"category": "Observability",
|
||
"description": (
|
||
"Attach personally identifiable information (PII) such as IP addresses "
|
||
"to Sentry events. Disabled by default for GDPR/CCPA compliance."
|
||
),
|
||
"type": "boolean",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": True,
|
||
},
|
||
}
|
||
|
||
|
||
def get_setting_from_db(db: Session, key: str) -> Optional[str]:
|
||
"""
|
||
Retrieve a setting value from the database.
|
||
|
||
Automatically decrypts sensitive values if encryption is enabled.
|
||
|
||
Args:
|
||
db: Database session
|
||
key: Setting key to retrieve
|
||
|
||
Returns:
|
||
Setting value as string (decrypted if necessary), or None if not found
|
||
"""
|
||
try:
|
||
setting = db.query(ApplicationSettings).filter(ApplicationSettings.key == key).first()
|
||
if not setting:
|
||
return None
|
||
|
||
# Check if this setting is sensitive and should be decrypted
|
||
metadata = get_setting_metadata(key)
|
||
if metadata.get("sensitive", False):
|
||
from app.utils.encryption import decrypt_value
|
||
|
||
return decrypt_value(setting.value)
|
||
|
||
return setting.value
|
||
except SQLAlchemyError as e:
|
||
logger.error(f"Error retrieving setting {key} from database: {e}")
|
||
return None
|
||
|
||
|
||
def save_setting_to_db(db: Session, key: str, value: Optional[str], changed_by: str = "system") -> bool:
|
||
"""
|
||
Save or update a setting in the database.
|
||
|
||
Automatically encrypts sensitive values if encryption is enabled.
|
||
Records an entry in the settings audit log.
|
||
|
||
Args:
|
||
db: Database session
|
||
key: Setting key
|
||
value: Setting value (as string)
|
||
changed_by: Username of the admin performing the change (for audit log)
|
||
|
||
Returns:
|
||
True if successful, False otherwise
|
||
"""
|
||
try:
|
||
# Check if this setting is sensitive and should be encrypted
|
||
metadata = get_setting_metadata(key)
|
||
storage_value = value
|
||
|
||
if metadata.get("sensitive", False) and value:
|
||
from app.utils.encryption import encrypt_value, is_encryption_available
|
||
|
||
if is_encryption_available():
|
||
storage_value = encrypt_value(value)
|
||
logger.debug(f"Encrypted sensitive setting: {key}")
|
||
else:
|
||
logger.warning(f"Storing sensitive setting {key} in plaintext (encryption unavailable)")
|
||
|
||
setting = db.query(ApplicationSettings).filter(ApplicationSettings.key == key).first()
|
||
old_storage_value = setting.value if setting else None
|
||
|
||
if setting:
|
||
setting.value = storage_value
|
||
else:
|
||
setting = ApplicationSettings(key=key, value=storage_value)
|
||
db.add(setting)
|
||
|
||
# Determine human-readable old value for audit log (decrypt if needed)
|
||
old_display_value = None
|
||
if old_storage_value is not None:
|
||
if metadata.get("sensitive", False):
|
||
try:
|
||
from app.utils.encryption import decrypt_value
|
||
|
||
old_display_value = decrypt_value(old_storage_value)
|
||
except Exception:
|
||
old_display_value = old_storage_value
|
||
else:
|
||
old_display_value = old_storage_value
|
||
|
||
# Write audit log entry
|
||
audit_entry = SettingsAuditLog(
|
||
key=key,
|
||
old_value=old_display_value,
|
||
new_value=value,
|
||
changed_by=changed_by,
|
||
action="update",
|
||
)
|
||
db.add(audit_entry)
|
||
|
||
db.commit()
|
||
logger.info(f"Saved setting {key} to database (changed_by={changed_by})")
|
||
return True
|
||
except SQLAlchemyError as e:
|
||
logger.error(f"Error saving setting {key} to database: {e}")
|
||
db.rollback()
|
||
return False
|
||
|
||
|
||
def get_all_settings_from_db(db: Session) -> Dict[str, str]:
|
||
"""
|
||
Retrieve all settings from the database.
|
||
|
||
Automatically decrypts sensitive values if encryption is enabled.
|
||
|
||
Args:
|
||
db: Database session
|
||
|
||
Returns:
|
||
Dictionary of setting key-value pairs (decrypted)
|
||
"""
|
||
try:
|
||
settings = db.query(ApplicationSettings).all()
|
||
result = {}
|
||
|
||
for setting in settings:
|
||
# Check if this setting is sensitive and should be decrypted
|
||
metadata = get_setting_metadata(setting.key)
|
||
if metadata.get("sensitive", False):
|
||
from app.utils.encryption import decrypt_value
|
||
|
||
result[setting.key] = decrypt_value(setting.value)
|
||
else:
|
||
result[setting.key] = setting.value
|
||
|
||
return result
|
||
except SQLAlchemyError as e:
|
||
logger.error(f"Error retrieving all settings from database: {e}")
|
||
return {}
|
||
|
||
|
||
def delete_setting_from_db(db: Session, key: str, changed_by: str = "system") -> bool:
|
||
"""
|
||
Delete a setting from the database.
|
||
|
||
Records an entry in the settings audit log.
|
||
|
||
Args:
|
||
db: Database session
|
||
key: Setting key to delete
|
||
changed_by: Username of the admin performing the change (for audit log)
|
||
|
||
Returns:
|
||
True if successful, False otherwise
|
||
"""
|
||
try:
|
||
setting = db.query(ApplicationSettings).filter(ApplicationSettings.key == key).first()
|
||
if setting:
|
||
# Capture old value for audit log (decrypt if sensitive)
|
||
metadata = get_setting_metadata(key)
|
||
old_display_value = None
|
||
if setting.value is not None:
|
||
if metadata.get("sensitive", False):
|
||
try:
|
||
from app.utils.encryption import decrypt_value
|
||
|
||
old_display_value = decrypt_value(setting.value)
|
||
except Exception:
|
||
old_display_value = setting.value
|
||
else:
|
||
old_display_value = setting.value
|
||
|
||
db.delete(setting)
|
||
|
||
audit_entry = SettingsAuditLog(
|
||
key=key,
|
||
old_value=old_display_value,
|
||
new_value=None,
|
||
changed_by=changed_by,
|
||
action="delete",
|
||
)
|
||
db.add(audit_entry)
|
||
|
||
db.commit()
|
||
logger.info(f"Deleted setting {key} from database (changed_by={changed_by})")
|
||
return True
|
||
return False
|
||
except SQLAlchemyError as e:
|
||
logger.error(f"Error deleting setting {key} from database: {e}")
|
||
db.rollback()
|
||
return False
|
||
|
||
|
||
def get_setting_metadata(key: str) -> Dict[str, Any]:
|
||
"""
|
||
Get metadata for a specific setting.
|
||
|
||
Args:
|
||
key: Setting key
|
||
|
||
Returns:
|
||
Dictionary containing setting metadata
|
||
"""
|
||
return SETTING_METADATA.get(
|
||
key,
|
||
{
|
||
"category": "Other",
|
||
"description": f"Setting: {key}",
|
||
"type": "string",
|
||
"sensitive": False,
|
||
"required": False,
|
||
"restart_required": False,
|
||
},
|
||
)
|
||
|
||
|
||
def get_settings_by_category() -> Dict[str, List[str]]:
|
||
"""
|
||
Get settings organized by category.
|
||
|
||
Returns:
|
||
Dictionary mapping category names to lists of setting keys
|
||
"""
|
||
categories = {}
|
||
for key, metadata in SETTING_METADATA.items():
|
||
category = metadata.get("category", "Other")
|
||
if category not in categories:
|
||
categories[category] = []
|
||
categories[category].append(key)
|
||
return categories
|
||
|
||
|
||
def validate_setting_value(key: str, value: str) -> Tuple[bool, Optional[str]]:
|
||
"""
|
||
Validate a setting value based on its metadata.
|
||
|
||
Args:
|
||
key: Setting key
|
||
value: Setting value to validate
|
||
|
||
Returns:
|
||
Tuple of (is_valid, error_message)
|
||
"""
|
||
metadata = get_setting_metadata(key)
|
||
setting_type = metadata.get("type", "string")
|
||
|
||
# Check required fields
|
||
if metadata.get("required", False) and not value:
|
||
return False, f"{key} is required"
|
||
|
||
# Type-specific validation
|
||
if setting_type == "boolean":
|
||
if value.lower() not in ["true", "false", "1", "0", "yes", "no"]:
|
||
return False, f"{key} must be a boolean value (true/false)"
|
||
|
||
elif setting_type == "integer":
|
||
try:
|
||
int(value)
|
||
except ValueError:
|
||
return False, f"{key} must be an integer"
|
||
|
||
elif setting_type == "slider":
|
||
try:
|
||
num = float(value)
|
||
min_val = metadata.get("min")
|
||
max_val = metadata.get("max")
|
||
if min_val is not None and num < min_val:
|
||
return False, f"{key} must be >= {min_val}"
|
||
if max_val is not None and num > max_val:
|
||
return False, f"{key} must be <= {max_val}"
|
||
except ValueError:
|
||
return False, f"{key} must be a number"
|
||
|
||
# Special validation for specific keys
|
||
if key == "session_secret" and value and len(value) < 32:
|
||
return False, "session_secret must be at least 32 characters"
|
||
|
||
return True, None
|
||
|
||
|
||
def get_audit_log(db: Session, limit: int = 100, offset: int = 0) -> List[Dict[str, Any]]:
|
||
"""
|
||
Retrieve the settings audit log, most recent first.
|
||
|
||
Sensitive values are masked in the returned list so the log is safe to
|
||
display in the admin UI without leaking secrets.
|
||
|
||
Args:
|
||
db: Database session
|
||
limit: Maximum number of entries to return
|
||
offset: Number of entries to skip (for pagination)
|
||
|
||
Returns:
|
||
List of audit log entry dicts ordered by changed_at descending
|
||
"""
|
||
try:
|
||
entries = (
|
||
db.query(SettingsAuditLog).order_by(SettingsAuditLog.changed_at.desc()).limit(limit).offset(offset).all()
|
||
)
|
||
result = []
|
||
for entry in entries:
|
||
meta = get_setting_metadata(entry.key)
|
||
is_sensitive = meta.get("sensitive", False)
|
||
result.append(
|
||
{
|
||
"id": entry.id,
|
||
"key": entry.key,
|
||
"old_value": ("[REDACTED]" if is_sensitive and entry.old_value else entry.old_value),
|
||
"new_value": ("[REDACTED]" if is_sensitive and entry.new_value else entry.new_value),
|
||
"changed_by": entry.changed_by,
|
||
"changed_at": (entry.changed_at.isoformat() if entry.changed_at else None),
|
||
"action": entry.action,
|
||
}
|
||
)
|
||
return result
|
||
except SQLAlchemyError as e:
|
||
logger.error(f"Error retrieving audit log: {e}")
|
||
return []
|
||
|
||
|
||
def get_setting_history(db: Session, key: str) -> List[Dict[str, Any]]:
|
||
"""
|
||
Retrieve the change history for a specific setting key, most recent first.
|
||
|
||
Sensitive values are masked so the response is safe to surface in the UI.
|
||
|
||
Args:
|
||
db: Database session
|
||
key: Setting key
|
||
|
||
Returns:
|
||
List of audit log entry dicts for this key
|
||
"""
|
||
try:
|
||
entries = (
|
||
db.query(SettingsAuditLog)
|
||
.filter(SettingsAuditLog.key == key)
|
||
.order_by(SettingsAuditLog.changed_at.desc())
|
||
.all()
|
||
)
|
||
meta = get_setting_metadata(key)
|
||
is_sensitive = meta.get("sensitive", False)
|
||
result = []
|
||
for entry in entries:
|
||
result.append(
|
||
{
|
||
"id": entry.id,
|
||
"key": entry.key,
|
||
"old_value": ("[REDACTED]" if is_sensitive and entry.old_value else entry.old_value),
|
||
"new_value": ("[REDACTED]" if is_sensitive and entry.new_value else entry.new_value),
|
||
"changed_by": entry.changed_by,
|
||
"changed_at": (entry.changed_at.isoformat() if entry.changed_at else None),
|
||
"action": entry.action,
|
||
}
|
||
)
|
||
return result
|
||
except SQLAlchemyError as e:
|
||
logger.error(f"Error retrieving history for setting {key}: {e}")
|
||
return []
|
||
|
||
|
||
def rollback_setting(db: Session, key: str, history_id: int, changed_by: str = "system") -> bool:
|
||
"""
|
||
Revert a setting to the value it had *before* a specific audit log entry.
|
||
|
||
The value stored in the chosen history entry's ``old_value`` field is
|
||
re-applied as the current database value, effectively undoing that change.
|
||
If ``old_value`` is ``None`` (i.e. the setting did not exist before that
|
||
change) the setting is removed from the database entirely, reverting to
|
||
ENV/defaults.
|
||
|
||
A new audit log entry is written to record the rollback operation.
|
||
|
||
Args:
|
||
db: Database session
|
||
key: Setting key to roll back
|
||
history_id: ID of the SettingsAuditLog entry whose ``old_value``
|
||
should become the restored value
|
||
changed_by: Username performing the rollback (for audit log)
|
||
|
||
Returns:
|
||
True if successful, False if the history entry was not found or an
|
||
error occurred
|
||
"""
|
||
try:
|
||
history_entry = (
|
||
db.query(SettingsAuditLog).filter(SettingsAuditLog.id == history_id, SettingsAuditLog.key == key).first()
|
||
)
|
||
if not history_entry:
|
||
logger.warning(f"Rollback failed: audit log entry {history_id} not found for key '{key}'")
|
||
return False
|
||
|
||
target_value = history_entry.old_value
|
||
|
||
if target_value is None:
|
||
# The old value was empty – remove the current db value to revert to ENV/default
|
||
return delete_setting_from_db(db, key, changed_by=changed_by)
|
||
else:
|
||
return save_setting_to_db(db, key, target_value, changed_by=changed_by)
|
||
except SQLAlchemyError as e:
|
||
logger.error(f"Error rolling back setting {key} to history entry {history_id}: {e}")
|
||
db.rollback()
|
||
return False
|
||
|
||
|
||
def get_settings_for_export(db: Session, source: str = "db") -> Dict[str, str]:
|
||
"""
|
||
Collect settings for export as environment variables.
|
||
|
||
Args:
|
||
db: Database session
|
||
source: ``"db"`` to export only database-persisted settings (default);
|
||
``"effective"`` to export the full current runtime configuration
|
||
(DB overrides ENV overrides application defaults) for every key
|
||
listed in SETTING_METADATA.
|
||
|
||
Returns:
|
||
Ordered dict mapping uppercase ENV variable names to their string values.
|
||
Sensitive values are included (the caller is responsible for access control).
|
||
"""
|
||
if source == "effective":
|
||
from app.config import settings as app_settings
|
||
|
||
db_settings = get_all_settings_from_db(db)
|
||
result = {}
|
||
for key in sorted(SETTING_METADATA.keys()):
|
||
# DB wins, then live settings object (ENV/default)
|
||
if key in db_settings and db_settings[key] is not None:
|
||
value = db_settings[key]
|
||
else:
|
||
value = getattr(app_settings, key, None)
|
||
if value is not None:
|
||
result[key.upper()] = str(value)
|
||
return result
|
||
else:
|
||
# DB only
|
||
db_settings = get_all_settings_from_db(db)
|
||
return {k.upper(): v for k, v in sorted(db_settings.items()) if v is not None}
|