123 lines
4.5 KiB
Python
123 lines
4.5 KiB
Python
"""mosquitto.conf generator — PostgreSQL model to config text (spec sections 61, 25).
|
|
|
|
The generated configuration MUST avoid the options deprecated in
|
|
Mosquitto 2.1 (password_file, acl_file, per_listener_settings): those are
|
|
removed in 3.0 (spec section 3.1 note).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.models import BrokerSettings, ListenerConfig
|
|
|
|
_BOOL = {True: "true", False: "false"}
|
|
|
|
|
|
@dataclass
|
|
class GenerationResult:
|
|
config_text: str
|
|
warnings: list[str] = field(default_factory=list)
|
|
|
|
|
|
def _global_section(db: Session, warnings: list[str]) -> list[str]:
|
|
lines: list[str] = []
|
|
for setting in db.scalars(select(BrokerSettings)).all():
|
|
key = setting.key.strip()
|
|
value = setting.value.strip()
|
|
if not key or key.startswith("#"):
|
|
continue
|
|
lines.append(f"{key} {value}")
|
|
return lines
|
|
|
|
|
|
def _listener_block(listener: ListenerConfig, warnings: list[str]) -> list[str]:
|
|
lines: list[str] = []
|
|
if not listener.enabled:
|
|
return lines
|
|
lines.append(f"listener {listener.port} {listener.bind_address or ''}".rstrip())
|
|
if listener.bind_address is None:
|
|
# `listener <port>` without bind address binds all interfaces.
|
|
lines[-1] = f"listener {listener.port}"
|
|
lines.append(f"protocol {listener.protocol}")
|
|
if listener.allow_anonymous:
|
|
lines.append("listener_allow_anonymous true")
|
|
if listener.max_connections != -1:
|
|
lines.append(f"max_connections {listener.max_connections}")
|
|
if listener.tls_enabled:
|
|
# TLS blocks only become valid once PKI ships (Release 2); we
|
|
# generate them now so the model is forward-compatible, but warn.
|
|
warnings.append(
|
|
f"listener '{listener.name}': TLS requested but PKI is not "
|
|
"available in Release 1; certfile/keyfile not set"
|
|
)
|
|
if listener.tls_min_version:
|
|
lines.append(f"tls_version {listener.tls_min_version}")
|
|
if listener.require_certificate:
|
|
lines.append("require_certificate true")
|
|
if listener.use_identity_as_username:
|
|
lines.append("use_identity_as_username true")
|
|
return lines
|
|
|
|
|
|
def generate_config(db: Session) -> GenerationResult:
|
|
"""Build the complete mosquitto.conf from the database model.
|
|
|
|
The platform ALWAYS regenerates the full file; it never edits the
|
|
active file in place (spec section 61).
|
|
"""
|
|
warnings: list[str] = []
|
|
|
|
listeners = db.scalars(select(ListenerConfig)).all()
|
|
enabled = [item for item in listeners if item.enabled]
|
|
|
|
lines: list[str] = []
|
|
lines.append("# Generated by MQTT Management Platform — DO NOT EDIT BY HAND")
|
|
lines.append("# Ownership: PostgreSQL model -> revision -> this file (spec 61)")
|
|
lines.append("")
|
|
|
|
lines.append("# --- Global settings ---")
|
|
# NOTE: per_listener_settings is deprecated in 2.1 (removed in 3.0).
|
|
# Plugins loaded via global_plugin apply to all listeners, and auth
|
|
# defaults are already global, so the option is simply omitted.
|
|
lines.append("allow_anonymous false")
|
|
global_lines = _global_section(db, warnings)
|
|
if global_lines:
|
|
lines.extend(global_lines)
|
|
lines.append("")
|
|
|
|
# Dynamic security plugin: global, so it applies to every listener.
|
|
lines.append("# --- Dynamic Security ---")
|
|
lines.append("global_plugin /usr/lib/mosquitto_dynamic_security.so")
|
|
lines.append("plugin_opt_config_file /mosquitto/config/dynamic-security.json")
|
|
lines.append("")
|
|
|
|
if enabled:
|
|
lines.append("# --- Listeners ---")
|
|
for listener in enabled:
|
|
block = _listener_block(listener, warnings)
|
|
if block:
|
|
lines.append(f"# listener: {listener.name}")
|
|
lines.extend(block)
|
|
lines.append("")
|
|
else:
|
|
warnings.append(
|
|
"no enabled listeners; broker would fall back to loopback-only "
|
|
"with anonymous access — refusing to generate"
|
|
)
|
|
raise ValueError("no enabled listeners in configuration model")
|
|
|
|
lines.append("# --- Persistence ---")
|
|
lines.append("persistence true")
|
|
lines.append("persistence_location /mosquitto/data/")
|
|
lines.append("")
|
|
|
|
lines.append("# --- Logging ---")
|
|
lines.append("log_dest stdout")
|
|
lines.append("log_timestamp true")
|
|
lines.append("log_timestamp_format %Y-%m-%dT%H:%M:%S")
|
|
|
|
return GenerationResult(config_text="\n".join(lines) + "\n", warnings=warnings) |