Add config reload via SIGHUP

This commit is contained in:
2026-01-23 22:21:34 +01:00
parent bd0c930060
commit a1e3ee1770
3 changed files with 66 additions and 5 deletions

View File

@@ -1,12 +1,13 @@
"""Application class - central dependency holder.""" """Application class - central dependency holder."""
import argon2
import logging import logging
import threading import threading
import argon2 from .config import load_config
from .dns import DNSService from .dns import DNSService
from .email import EmailService from .email import EmailService
from .logging import setup_logging
from .models import create_tables, init_database from .models import create_tables, init_database
from .ratelimit import BadLimiter, GoodLimiter from .ratelimit import BadLimiter, GoodLimiter
@@ -18,14 +19,16 @@ class Application:
Holds configuration and all service instances. Holds configuration and all service instances.
""" """
def __init__(self, config): def __init__(self, config, config_path=None):
""" """
Initialize application with configuration. Initialize application with configuration.
Args: Args:
config: Configuration dictionary from TOML file. config: Configuration dictionary from TOML file.
config_path: Path to configuration file (for reload).
""" """
self.config = config self.config = config
self.config_path = config_path
self.password_hasher = argon2.PasswordHasher() self.password_hasher = argon2.PasswordHasher()
self.shutdown_event = threading.Event() self.shutdown_event = threading.Event()
@@ -57,6 +60,39 @@ class Application:
self.bad_limiter = BadLimiter(self.config) self.bad_limiter = BadLimiter(self.config)
logging.info("Rate limiters initialized") logging.info("Rate limiters initialized")
def reload_config(self):
"""
Reload configuration from file.
Does not reload: database settings, host, port.
"""
new_config = load_config(self.config_path)
# Preserve DB and bind settings
new_config["database"] = self.config["database"]
new_config["daemon"]["host"] = self.config["daemon"]["host"]
new_config["daemon"]["port"] = self.config["daemon"]["port"]
self.config = new_config
# Reconfigure logging
setup_logging(
level=self.config["daemon"]["log_level"],
target=self.config["daemon"]["log_target"],
syslog_socket=self.config["daemon"]["syslog_socket"],
syslog_facility=self.config["daemon"]["syslog_facility"],
log_file=self.config["daemon"]["log_file"],
log_file_size=self.config["daemon"]["log_file_size"],
log_versions=self.config["daemon"]["log_versions"],
)
# Re-init services
self.init_dns()
self.init_email()
self.init_rate_limiters()
logging.info("Configuration reloaded")
def signal_shutdown(self): def signal_shutdown(self):
"""Signal the application to shut down.""" """Signal the application to shut down."""
logging.info("Shutdown signaled") logging.info("Shutdown signaled")

View File

@@ -153,7 +153,7 @@ def main():
) )
# Create application instance # Create application instance
app = Application(config) app = Application(config, config_path)
# Initialize database # Initialize database
try: try:

View File

@@ -461,14 +461,39 @@ def run_daemon(app):
expired_cleanup_thread = ExpiredRecordsCleanupThread(app) expired_cleanup_thread = ExpiredRecordsCleanupThread(app)
expired_cleanup_thread.start() expired_cleanup_thread.start()
# Setup signal handlers # Setup signal handlers
def signal_handler(signum, frame): def signal_handler(signum, frame):
logging.info(f"Signal received: {signum}, shutting down") logging.info(f"Signal received: {signum}, shutting down")
app.signal_shutdown() app.signal_shutdown()
def sighup_handler(signum, frame):
logging.info("SIGHUP received, reloading configuration")
try:
app.reload_config()
# Update server attributes
server.proxy_header = app.config["daemon"].get("proxy_header", "")
server.trusted_networks = _parse_trusted_proxies(
app.config["daemon"].get("trusted_proxies", [])
)
# Reload SSL if enabled
if app.config["daemon"]["ssl"]:
new_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
new_context.load_cert_chain(
app.config["daemon"]["ssl_cert_file"],
app.config["daemon"]["ssl_key_file"]
)
# Note: existing connections use old cert, new connections use new
server.socket = new_context.wrap_socket(
server.socket.detach(), server_side=True
)
except Exception as e:
logging.error(f"Config reload failed: {e}")
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGHUP, sighup_handler)
paths = ", ".join(ep["path"] for ep in config["endpoints"]) paths = ", ".join(ep["path"] for ep in config["endpoints"])
logging.info(f"Daemon started: {proto}://{host}:{port} endpoints=[{paths}]") logging.info(f"Daemon started: {proto}://{host}:{port} endpoints=[{paths}]")