Files
pic/api/managers.py
T
roof 743b026b01
Unit Tests / test (push) Successful in 13m22s
feat: connectivity redesign phase 7 — cell-relay as a connection type
cell exits surface as cell_relay connections via reconcile, bridged onto
the existing cell route_via mechanism, health from handshake, loop
detection, assignable in the unified UI

- CELL_RELAY_TYPE constant; not manually creatable
- reconcile_cell_relays() derives connections from cell links offering an
  exit (name "Cell: <cellname>", mark+table only, no iface/port/container)
- apply_routes bridges cell_relay to existing route_via path via
  apply_peer_route_via + cell firewall rules + set_exit_relay_active;
  keeps peer.route_via in sync
- _probe_cell_relay health from cell handshake + offer state
- _cell_relay_loops loop detection at assign and apply time
- FAILOPEN_DEFAULTS cell_relay=False
- set_peer_exit clears stale route_via on reassignment
- reconcile hooked into PUT /exit-offer and peer-sync/permissions handlers
- cell_link_manager + wireguard_manager wired into connectivity_manager
- UI: cell_relay in TYPE_META/GROUP_TYPES/GROUP_LABELS (Cells optgroup),
  removed "coming soon" placeholder
- 18 new tests in tests/test_connectivity_cell_relay.py

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 23:58:19 -04:00

171 lines
7.4 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
from audit_manager import AuditManager
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)
# Connectivity brings one container up per connection instance via the composer;
# wire it now that the composer exists (composer is built after connectivity).
connectivity_manager.service_composer = service_composer
# cell_relay connections are derived from cell links and route through the cell
# WG tunnel; wire the managers that drive that path + handshake-based health.
connectivity_manager.cell_link_manager = cell_link_manager
connectivity_manager.wireguard_manager = wireguard_manager
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
audit_manager = AuditManager(data_dir=DATA_DIR, config_dir=CONFIG_DIR)
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', 'audit_manager',
'firewall_manager', 'EventType',
'DATA_DIR', 'CONFIG_DIR',
]