Add config reload via SIGHUP
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
"""Application class - central dependency holder."""
|
||||
|
||||
import argon2
|
||||
import logging
|
||||
import threading
|
||||
|
||||
import argon2
|
||||
|
||||
from .config import load_config
|
||||
from .dns import DNSService
|
||||
from .email import EmailService
|
||||
from .logging import setup_logging
|
||||
from .models import create_tables, init_database
|
||||
from .ratelimit import BadLimiter, GoodLimiter
|
||||
|
||||
@@ -18,14 +19,16 @@ class Application:
|
||||
Holds configuration and all service instances.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
def __init__(self, config, config_path=None):
|
||||
"""
|
||||
Initialize application with configuration.
|
||||
|
||||
Args:
|
||||
config: Configuration dictionary from TOML file.
|
||||
config_path: Path to configuration file (for reload).
|
||||
"""
|
||||
self.config = config
|
||||
self.config_path = config_path
|
||||
self.password_hasher = argon2.PasswordHasher()
|
||||
self.shutdown_event = threading.Event()
|
||||
|
||||
@@ -57,6 +60,39 @@ class Application:
|
||||
self.bad_limiter = BadLimiter(self.config)
|
||||
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):
|
||||
"""Signal the application to shut down."""
|
||||
logging.info("Shutdown signaled")
|
||||
|
||||
@@ -153,7 +153,7 @@ def main():
|
||||
)
|
||||
|
||||
# Create application instance
|
||||
app = Application(config)
|
||||
app = Application(config, config_path)
|
||||
|
||||
# Initialize database
|
||||
try:
|
||||
|
||||
@@ -461,14 +461,39 @@ def run_daemon(app):
|
||||
expired_cleanup_thread = ExpiredRecordsCleanupThread(app)
|
||||
expired_cleanup_thread.start()
|
||||
|
||||
|
||||
# Setup signal handlers
|
||||
def signal_handler(signum, frame):
|
||||
logging.info(f"Signal received: {signum}, shutting down")
|
||||
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.SIGINT, signal_handler)
|
||||
signal.signal(signal.SIGHUP, sighup_handler)
|
||||
|
||||
paths = ", ".join(ep["path"] for ep in config["endpoints"])
|
||||
logging.info(f"Daemon started: {proto}://{host}:{port} endpoints=[{paths}]")
|
||||
|
||||
Reference in New Issue
Block a user