mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
84 lines
3.4 KiB
Python
84 lines
3.4 KiB
Python
"""Конфигурация relay. Единственный источник — переменные окружения.
|
|
|
|
Секреты (токен доставки) читаются из ФАЙЛА, путь к которому задан переменной:
|
|
значение секрета не попадает ни в командную строку, ни в `systemctl show`,
|
|
ни в вывод `ps`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
# §7.5 ТЗ: вложение ≤ 8 MiB, весь запрос ≤ 8.5 MiB.
|
|
MAX_REQUEST_BYTES = 8 * 1024 * 1024 + 512 * 1024
|
|
MAX_ATTACHMENT_BYTES = 8 * 1024 * 1024
|
|
MAX_MESSAGE_CODEPOINTS = 10_000
|
|
MAX_CONTACT_CODEPOINTS = 320
|
|
|
|
# §9.2 ТЗ: 5 попыток в час и 20 в сутки на источник.
|
|
RATE_HOURLY = 5
|
|
RATE_DAILY = 20
|
|
# Глобальный предохранитель: столько принятых отчётов в час со всех источников.
|
|
RATE_GLOBAL_HOURLY = 60
|
|
|
|
IDEMPOTENCY_TTL_SECONDS = 24 * 60 * 60
|
|
RATE_TTL_SECONDS = 24 * 60 * 60
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Config:
|
|
port: int
|
|
spool: Path
|
|
mode: str # 'deliver' | 'discard'
|
|
channel: str # 'telegram' | 'ha_webhook'
|
|
enabled: bool
|
|
retention_days: int
|
|
telegram_token: str # пусто = доставка выключена
|
|
telegram_chat_id: str
|
|
webhook_url: str # адрес вебхука Home Assistant (секрет: он же ключ доступа)
|
|
trusted_proxy: bool # брать источник из X-Forwarded-For
|
|
|
|
@property
|
|
def delivers(self) -> bool:
|
|
if self.mode != "deliver":
|
|
return False
|
|
if self.channel == "ha_webhook":
|
|
return bool(self.webhook_url)
|
|
return bool(self.telegram_token and self.telegram_chat_id)
|
|
|
|
|
|
def _read_secret(path_value: str) -> str:
|
|
if not path_value:
|
|
return ""
|
|
path = Path(path_value)
|
|
if not path.is_file():
|
|
return ""
|
|
return path.read_text(encoding="utf-8").strip()
|
|
|
|
|
|
def load(env: dict[str, str] | None = None) -> Config:
|
|
env = dict(os.environ if env is None else env)
|
|
mode = env.get("HP_RELAY_MODE", "discard").strip().lower()
|
|
if mode not in {"deliver", "discard"}:
|
|
raise ValueError("HP_RELAY_MODE must be 'deliver' or 'discard'")
|
|
channel = env.get("HP_RELAY_CHANNEL", "telegram").strip().lower()
|
|
if channel not in {"telegram", "ha_webhook"}:
|
|
raise ValueError("HP_RELAY_CHANNEL must be 'telegram' or 'ha_webhook'")
|
|
spool = Path(env.get("HP_RELAY_SPOOL", "/var/lib/hp-support-relay"))
|
|
return Config(
|
|
port=int(env.get("HP_RELAY_PORT", "8130")),
|
|
spool=spool,
|
|
mode=mode,
|
|
channel=channel,
|
|
# Рубильник §19 ТЗ: выключенный relay обязан отвечать единообразным 503,
|
|
# а не принимать отчёты и терять их.
|
|
enabled=env.get("HP_RELAY_ENABLED", "1").strip() not in {"0", "false", "no"},
|
|
retention_days=int(env.get("HP_RELAY_RETENTION_DAYS", "30")),
|
|
telegram_token=_read_secret(env.get("HP_RELAY_TELEGRAM_TOKEN_FILE", "")),
|
|
telegram_chat_id=env.get("HP_RELAY_TELEGRAM_CHAT_ID", "").strip(),
|
|
webhook_url=_read_secret(env.get("HP_RELAY_WEBHOOK_URL_FILE", "")),
|
|
trusted_proxy=env.get("HP_RELAY_TRUSTED_PROXY", "1").strip() not in {"0", "false", "no"},
|
|
)
|