"""One-shot cleanup of legacy builtin containers from the old main compose stack.""" import logging import subprocess logger = logging.getLogger('picell') _LEGACY_BUILTIN_CONTAINERS = [ 'cell-mail', 'cell-rainloop', 'cell-radicale', 'cell-webdav', 'cell-filegator', ] def cleanup_legacy_builtin_containers(config_manager) -> None: """Remove legacy containers whose compose project is 'pic' (main stack). Idempotent — guarded by _meta.legacy_builtins_cleaned in cell_config.json. Containers from per-service installs (project != 'pic') are left untouched. """ try: already_done = config_manager.configs.get('_meta', {}).get('legacy_builtins_cleaned', False) if already_done: return except Exception: return removed = [] for cname in _LEGACY_BUILTIN_CONTAINERS: try: inspect = subprocess.run( ['docker', 'inspect', cname, '--format', '{{index .Config.Labels "com.docker.compose.project"}}'], capture_output=True, text=True, timeout=10, ) if inspect.returncode != 0: continue project = inspect.stdout.strip() if project != 'pic': continue subprocess.run(['docker', 'stop', cname], capture_output=True, timeout=30) subprocess.run(['docker', 'rm', cname], capture_output=True, timeout=30) removed.append(cname) except Exception as exc: logger.warning('cleanup_legacy_builtin_containers: %s: %s', cname, exc) try: meta = dict(config_manager.configs.get('_meta', {})) meta['legacy_builtins_cleaned'] = True config_manager.configs['_meta'] = meta config_manager._save_all_configs() except Exception as exc: logger.warning('cleanup_legacy_builtin_containers: failed to set sentinel: %s', exc) if removed: logger.info('Removed legacy builtin containers: %s', ', '.join(removed))