13074f56cb
Unit Tests / test (push) Successful in 12m34s
Root causes fixed:
- Dead LOG_LEVEL globals() lookup pinned root logger at INFO regardless of
PIC_LOG_LEVEL env or config; replaced with _resolve_root_log_level() +
apply_root_log_level() which sets both root logger and all attached handlers
at startup and on runtime re-apply.
- set_service_level() only set the named 'pic.<service>' logger; bare module
loggers (e.g. 'caddy_manager') were never reached, so per-service log files
stayed 0 bytes. Fixed via _SERVICE_MODULE_LOGGERS map covering all managers.
- Log viewer GET /api/logs had no level filter; added ?level= query param.
- Per-service log levels lived in an out-of-band config/api/log_levels.json
side-file with no validation; migrated into ConfigManager under a new
'logging' section ({python:{root,services}, containers:{caddy,coredns,
wireguard,mailserver,api}}) with get/set helpers, invalid-level rejection,
and one-time migration from the old file on first load.
New capabilities:
- Container log levels: Caddy (injects global log { level X } + hot reload),
CoreDNS (DEBUG enables log plugin, else errors-only), WireGuard/mailserver
via pending_restart path.
- PUT /api/logs/verbosity accepts {python, containers} dict; returns per-entry
applied:hot|pending_restart status.
- Webui Logs page gains two-section Verbosity tab (Python services + Container
services) with needs-restart badges.
- managers.py wires per-service loggers before manager instantiation and
re-applies persisted levels from ConfigManager; legacy log_levels.json read
removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
161 lines
6.7 KiB
Python
161 lines
6.7 KiB
Python
"""
|
|
Manager singletons for the PIC API.
|
|
|
|
All service managers are instantiated here and imported by app.py. Routes in
|
|
app.py reference these by name from app's own namespace (so test patches via
|
|
`patch('app.email_manager', mock)` continue to work as before).
|
|
|
|
Directory/path env vars:
|
|
DATA_DIR — host-mapped persistent data directory (default: /app/data)
|
|
CONFIG_DIR — host-mapped config directory (default: /app/config)
|
|
"""
|
|
|
|
import os
|
|
|
|
from network_manager import NetworkManager
|
|
from wireguard_manager import WireGuardManager
|
|
from peer_registry import PeerRegistry
|
|
from email_manager import EmailManager
|
|
from calendar_manager import CalendarManager
|
|
from file_manager import FileManager
|
|
from routing_manager import RoutingManager
|
|
from vault_manager import VaultManager
|
|
from container_manager import ContainerManager
|
|
from config_manager import ConfigManager
|
|
from service_bus import ServiceBus, EventType
|
|
from log_manager import LogManager
|
|
from cell_link_manager import CellLinkManager
|
|
import firewall_manager
|
|
from auth_manager import AuthManager
|
|
from setup_manager import SetupManager
|
|
from caddy_manager import CaddyManager
|
|
from ddns_manager import DDNSManager
|
|
from connectivity_manager import ConnectivityManager
|
|
from service_registry import ServiceRegistry
|
|
from service_composer import ServiceComposer
|
|
from account_manager import AccountManager
|
|
|
|
DATA_DIR = os.environ.get('DATA_DIR', '/app/data')
|
|
CONFIG_DIR = os.environ.get('CONFIG_DIR', '/app/config')
|
|
|
|
config_manager = ConfigManager(
|
|
config_file=os.path.join(CONFIG_DIR, 'cell_config.json'),
|
|
data_dir=DATA_DIR,
|
|
)
|
|
service_bus = ServiceBus()
|
|
log_manager = LogManager(log_dir='./data/logs')
|
|
|
|
# Attach per-service file loggers BEFORE any manager is instantiated. Managers
|
|
# log during __init__ via self.logger ('picell.<svc>'); without the handlers in
|
|
# place first, those early records would be lost from the per-service log files.
|
|
_service_log_configs = {
|
|
'network': {'level': 'INFO', 'formatter': 'json', 'console': False},
|
|
'wireguard': {'level': 'INFO', 'formatter': 'json', 'console': False},
|
|
'email': {'level': 'INFO', 'formatter': 'json', 'console': False},
|
|
'calendar': {'level': 'INFO', 'formatter': 'json', 'console': False},
|
|
'files': {'level': 'INFO', 'formatter': 'json', 'console': False},
|
|
'routing': {'level': 'INFO', 'formatter': 'json', 'console': False},
|
|
'vault': {'level': 'INFO', 'formatter': 'json', 'console': False},
|
|
'api': {'level': 'INFO', 'formatter': 'json', 'console': True},
|
|
}
|
|
for _svc, _cfg in _service_log_configs.items():
|
|
log_manager.add_service_logger(_svc, _cfg)
|
|
|
|
# ServiceRegistry depends only on config_manager; create it early so
|
|
# NetworkManager and CaddyManager can derive subdomains from manifests
|
|
# instead of hardcoding service names.
|
|
service_registry = ServiceRegistry(config_manager=config_manager)
|
|
|
|
network_manager = NetworkManager(data_dir=DATA_DIR, config_dir=CONFIG_DIR,
|
|
service_registry=service_registry)
|
|
wireguard_manager = WireGuardManager(data_dir=DATA_DIR, config_dir=CONFIG_DIR)
|
|
peer_registry = PeerRegistry(data_dir=DATA_DIR, config_dir=CONFIG_DIR,
|
|
config_manager=config_manager)
|
|
email_manager = EmailManager(data_dir=DATA_DIR, config_dir=CONFIG_DIR, service_bus=service_bus)
|
|
calendar_manager = CalendarManager(data_dir=DATA_DIR, config_dir=CONFIG_DIR)
|
|
file_manager = FileManager(data_dir=DATA_DIR, config_dir=CONFIG_DIR)
|
|
routing_manager = RoutingManager(data_dir=DATA_DIR, config_dir=CONFIG_DIR)
|
|
vault_manager = VaultManager(data_dir=DATA_DIR, config_dir=CONFIG_DIR)
|
|
container_manager = ContainerManager(data_dir=DATA_DIR, config_dir=CONFIG_DIR)
|
|
cell_link_manager = CellLinkManager(
|
|
data_dir=DATA_DIR, config_dir=CONFIG_DIR,
|
|
wireguard_manager=wireguard_manager,
|
|
network_manager=network_manager,
|
|
)
|
|
auth_manager = AuthManager(data_dir=DATA_DIR, config_dir=CONFIG_DIR)
|
|
caddy_manager = CaddyManager(config_manager=config_manager, data_dir=DATA_DIR, config_dir=CONFIG_DIR,
|
|
service_bus=service_bus, service_registry=service_registry)
|
|
ddns_manager = DDNSManager(config_manager=config_manager, data_dir=DATA_DIR, config_dir=CONFIG_DIR,
|
|
service_bus=service_bus, service_registry=service_registry)
|
|
connectivity_manager = ConnectivityManager(
|
|
config_manager=config_manager,
|
|
peer_registry=peer_registry,
|
|
vault_manager=vault_manager,
|
|
data_dir=DATA_DIR,
|
|
config_dir=CONFIG_DIR,
|
|
)
|
|
|
|
service_composer = ServiceComposer(config_manager=config_manager, data_dir=DATA_DIR)
|
|
account_manager = AccountManager(
|
|
service_registry=service_registry,
|
|
data_dir=DATA_DIR,
|
|
config_manager=config_manager,
|
|
email_manager=email_manager,
|
|
calendar_manager=calendar_manager,
|
|
file_manager=file_manager,
|
|
)
|
|
|
|
from service_store_manager import ServiceStoreManager
|
|
service_store_manager = ServiceStoreManager(
|
|
config_manager=config_manager,
|
|
caddy_manager=caddy_manager,
|
|
container_manager=container_manager,
|
|
data_dir=DATA_DIR,
|
|
config_dir=CONFIG_DIR,
|
|
service_composer=service_composer,
|
|
)
|
|
|
|
from egress_manager import EgressManager
|
|
egress_manager = EgressManager(
|
|
config_manager=config_manager,
|
|
service_store_manager=service_store_manager,
|
|
connectivity_manager=connectivity_manager,
|
|
data_dir=DATA_DIR,
|
|
config_dir=CONFIG_DIR,
|
|
)
|
|
service_store_manager.egress_manager = egress_manager
|
|
|
|
setup_manager = SetupManager(config_manager=config_manager, auth_manager=auth_manager,
|
|
network_manager=network_manager)
|
|
|
|
# Apply persisted per-service log levels from ConfigManager (single source of
|
|
# truth — the logging section of cell_config). This runs AFTER managers are
|
|
# instantiated so it overrides their default INFO and reaches the module loggers.
|
|
try:
|
|
_logging_cfg = config_manager.get_logging_config()
|
|
for _svc, _lvl in _logging_cfg['python']['services'].items():
|
|
log_manager.set_service_level(_svc, _lvl)
|
|
except Exception:
|
|
pass
|
|
|
|
# Let generate_corefile keep the configured CoreDNS log level sticky across all
|
|
# regenerations, not just verbosity-triggered ones.
|
|
firewall_manager.set_coredns_level_resolver(
|
|
lambda: config_manager.get_logging_config()['containers'].get('coredns', 'INFO')
|
|
)
|
|
|
|
service_bus.start()
|
|
|
|
__all__ = [
|
|
'config_manager', 'service_bus', 'log_manager',
|
|
'network_manager', 'wireguard_manager', 'peer_registry',
|
|
'email_manager', 'calendar_manager', 'file_manager',
|
|
'routing_manager', 'vault_manager', 'container_manager',
|
|
'cell_link_manager', 'auth_manager', 'setup_manager', 'caddy_manager',
|
|
'ddns_manager', 'service_store_manager', 'connectivity_manager',
|
|
'service_registry', 'service_composer', 'account_manager',
|
|
'egress_manager',
|
|
'firewall_manager', 'EventType',
|
|
'DATA_DIR', 'CONFIG_DIR',
|
|
]
|