82 lines
3.5 KiB
Python
82 lines
3.5 KiB
Python
"""Configuration loaded from environment variables."""
|
|||
|
|
import os
|
||
|
|
from datetime import timedelta
|
||
|
|
|
||
|
|
|
||
|
|
def _bool(val: str, default: bool = False) -> bool:
|
||
|
|
if val is None:
|
||
|
|
return default
|
||
|
|
return val.strip().lower() in ("1", "true", "yes", "on")
|
||
|
|
|
||
|
|
|
||
|
|
class Config:
|
||
|
|
SECRET_KEY = os.environ.get("SECRET_KEY", "dev-only-replace-me")
|
||
|
|
FLASK_ENV = os.environ.get("FLASK_ENV", "production")
|
||
|
|
|
||
|
|
DATABASE_PATH = os.environ.get("DATABASE_PATH", "/data/alerthub.db")
|
||
|
|
SQLALCHEMY_DATABASE_URI = f"sqlite:///{DATABASE_PATH}"
|
||
|
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||
|
|
SQLALCHEMY_ENGINE_OPTIONS = {
|
||
|
|
"pool_pre_ping": True,
|
||
|
|
# Wait (instead of erroring) when another process holds the SQLite
|
||
|
|
# write lock — both gunicorn apps share one database file.
|
||
|
|
"connect_args": {"timeout": 30},
|
||
|
|
}
|
||
|
|
|
||
|
|
# Bootstrap admin (only used on first DB init)
|
||
|
|
INITIAL_ADMIN_USERNAME = os.environ.get("INITIAL_ADMIN_USERNAME", "admin")
|
||
|
|
INITIAL_ADMIN_EMAIL = os.environ.get("INITIAL_ADMIN_EMAIL", "admin@example.com")
|
||
|
|
INITIAL_ADMIN_PASSWORD = os.environ.get("INITIAL_ADMIN_PASSWORD", "ChangeMeOnFirstLogin!")
|
||
|
|
|
||
|
|
# Session security
|
||
|
|
SESSION_COOKIE_SECURE = _bool(os.environ.get("SESSION_COOKIE_SECURE"), False)
|
||
|
|
SESSION_COOKIE_HTTPONLY = True
|
||
|
|
SESSION_COOKIE_SAMESITE = "Lax"
|
||
|
|
PERMANENT_SESSION_LIFETIME = timedelta(
|
||
|
|
hours=int(os.environ.get("SESSION_LIFETIME_HOURS", "8"))
|
||
|
|
)
|
||
|
|
|
||
|
|
PASSWORD_MIN_LENGTH = int(os.environ.get("PASSWORD_MIN_LENGTH", "10"))
|
||
|
|
|
||
|
|
# MFA
|
||
|
|
MFA_ISSUER_NAME = os.environ.get("MFA_ISSUER_NAME", "AlertHub")
|
||
|
|
|
||
|
|
# Scheduler
|
||
|
|
SCHEDULER_INTERVAL_MINUTES = int(os.environ.get("SCHEDULER_INTERVAL_MINUTES", "60"))
|
||
|
|
|
||
|
|
# Default reminder lead times in days
|
||
|
|
DEFAULT_REMINDER_DAYS = os.environ.get("DEFAULT_REMINDER_DAYS", "30,14,7,1")
|
||
|
|
|
||
|
|
# SMTP defaults (seed only — admins edit live values via backoffice)
|
||
|
|
# Purelymail outgoing: smtp.purelymail.com : 465 (SSL/TLS)
|
||
|
|
SMTP_HOST = os.environ.get("SMTP_HOST", "smtp.purelymail.com")
|
||
|
|
SMTP_PORT = int(os.environ.get("SMTP_PORT", "465"))
|
||
|
|
SMTP_USERNAME = os.environ.get("SMTP_USERNAME", "")
|
||
|
|
SMTP_PASSWORD = os.environ.get("SMTP_PASSWORD", "")
|
||
|
|
SMTP_ENCRYPTION = os.environ.get("SMTP_ENCRYPTION", "ssl")
|
||
|
|
SMTP_FROM_ADDRESS = os.environ.get("SMTP_FROM_ADDRESS", "")
|
||
|
|
SMTP_FROM_NAME = os.environ.get("SMTP_FROM_NAME", "AlertHub")
|
||
|
|
|
||
|
|
# IMAP defaults (seed only) — used to save a copy of sent mail to the
|
||
|
|
# mailbox's Sent folder. Purelymail incoming: imap.purelymail.com : 993 (SSL/TLS)
|
||
|
|
IMAP_HOST = os.environ.get("IMAP_HOST", "imap.purelymail.com")
|
||
|
|
IMAP_PORT = int(os.environ.get("IMAP_PORT", "993"))
|
||
|
|
IMAP_USERNAME = os.environ.get("IMAP_USERNAME", "")
|
||
|
|
IMAP_PASSWORD = os.environ.get("IMAP_PASSWORD", "")
|
||
|
|
IMAP_SENT_FOLDER = os.environ.get("IMAP_SENT_FOLDER", "Sent")
|
||
|
|
|
||
|
|
# Ports (used by run scripts only)
|
||
|
|
FRONTOFFICE_PORT = int(os.environ.get("FRONTOFFICE_PORT", "8080"))
|
||
|
|
BACKOFFICE_PORT = int(os.environ.get("BACKOFFICE_PORT", "9001"))
|
||
|
|
|
||
|
|
# Optional explicit public URLs for the cross-console links in the top bar.
|
||
|
|
# If empty, the app derives them from the request host + the ports above
|
||
|
|
# (e.g. http://<your-host>:9001/). Set these when behind a reverse proxy
|
||
|
|
# or when the consoles are reached at custom hostnames/paths.
|
||
|
|
BACKOFFICE_PUBLIC_URL = os.environ.get("BACKOFFICE_PUBLIC_URL", "")
|
||
|
|
FRONTOFFICE_PUBLIC_URL = os.environ.get("FRONTOFFICE_PUBLIC_URL", "")
|
||
|
|
|
||
|
|
# Resource limits
|
||
|
|
MAX_CONTENT_LENGTH = 2 * 1024 * 1024 # 2MB upload cap
|
||
|
|
WTF_CSRF_TIME_LIMIT = 3600
|