Add config reload via SIGHUP
This commit is contained in:
@@ -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")
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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}]")
|
||||||
|
|||||||
Reference in New Issue
Block a user