From 76b06b157aeef0cda9d7cb47292f1a79d83c89cd Mon Sep 17 00:00:00 2001 From: Stitch505 <–Gleb@stitch505.su> Date: Fri, 22 May 2026 22:56:00 +0400 Subject: [PATCH] fix: deduplicate sidecars/systemd, block system-critical stops, filter runtime paths --- core/state.py | 49 --------------------------------------------- discover/network.py | 12 +++++++++-- discover/nginx.py | 6 ++++-- source/source.py | 38 ++++++++++++++++++++++++++++------- 4 files changed, 45 insertions(+), 60 deletions(-) diff --git a/core/state.py b/core/state.py index 6c6dc61..25a0671 100644 --- a/core/state.py +++ b/core/state.py @@ -137,55 +137,6 @@ def reset_state(mode=None): save_state(state) -def mark_completed(step): - state = load_state() - if step not in state["completed_steps"]: - state["completed_steps"].append(step) - save_state(state) - - -def is_completed(step): - return step in load_state().get("completed_steps", []) - - -def set_error(step, stdout, stderr, suggestion=""): - state = load_state() - state["paused"] = True - state["last_error"] = { - "step": step, - "timestamp": datetime.now().isoformat(), - "stdout": stdout, - "stderr": stderr, - "suggestion": suggestion, - } - save_state(state) - - -def clear_error(): - state = load_state() - state["paused"] = False - state["last_error"] = None - save_state(state) - - -def reset_state(mode=None): - """Сбрасывает состояние для нового запуска (source, target или полностью).""" - global _LAST_STATE - # Загружаем текущее, чтобы не потерять пути к архивам если пользователь хочет - # Но completed_steps и stage сбрасываем - state = load_state() - state["stage"] = "INIT" - state["mode"] = mode - state["completed_steps"] = [] - state["interrupted_at"] = None - state["resumable_hint"] = None - state["last_error"] = None - state["paused"] = False - state["updated_at"] = datetime.now().isoformat() - _LAST_STATE = state - save_state(state) - - def set_interrupted(): """Помечает, что скрипт был прерван (Ctrl+C, SIGTERM).""" state = load_state() diff --git a/discover/network.py b/discover/network.py index d1f97de..af7aaee 100644 --- a/discover/network.py +++ b/discover/network.py @@ -72,10 +72,12 @@ def find_listeners_on_host(port): def find_sidecar_processes(cid, container_ports): """ Ищет sidecar-процессы на хосте, к которым контейнер стучится через loopback. + Дедуплицирует по (host_process, container_port_target). """ info("Ищем loopback-соединения контейнера (sidecar / proxy / WARP) ...") conns = get_container_host_connections(cid) sidecars = [] + seen = set() for c in conns: local = c.get("local", "") @@ -87,11 +89,16 @@ def find_sidecar_processes(cid, container_ports): listeners = find_listeners_on_host(port) if listeners: for l in listeners: - info(f" Контейнер подключается к {local} → процесс на хосте: {l.get('process', '?')} (pid={l.get('pid', '?')})") + proc = l.get("process", "?") + key = (proc, port) + if key in seen: + continue + seen.add(key) + info(f" Контейнер подключается к {local} → процесс на хосте: {proc} (pid={l.get('pid', '?')})") sidecars.append({ "type": "loopback_listener", "container_port_target": port, - "host_process": l.get("process"), + "host_process": proc, "host_pid": l.get("pid"), "method": "ss_lsof", }) @@ -176,6 +183,7 @@ def gather_host_network_info(): def find_systemd_units_related(procs): """ Ищет systemd unit-файлы для процессов (sidecar и других). + Дедуплицирует по имени unit. """ units = [] seen = set() diff --git a/discover/nginx.py b/discover/nginx.py index 29c3afb..64fd610 100644 --- a/discover/nginx.py +++ b/discover/nginx.py @@ -205,8 +205,9 @@ def discover_nginx(service_ports, service_domain_hints): def get_nginx_systemd_unit(): - """Ищет unit-файл nginx""" + """Ищет unit-файл nginx, возвращает уникальные (deduplicated по пути)""" units = [] + seen_paths = set() for unit in ("nginx.service", "nginx"): out = run(f"systemctl is-active {unit}", check=False) if out.returncode == 0 or out.stdout.strip() in ("active", "inactive"): @@ -214,7 +215,8 @@ def get_nginx_systemd_unit(): try: uout = run(f"systemctl show {unit} -p FragmentPath --value", check=False) path = uout.stdout.strip() - if path: + if path and path not in seen_paths: + seen_paths.add(path) units.append({"unit": unit, "path": path}) except Exception: pass diff --git a/source/source.py b/source/source.py index acdee08..402bfa0 100644 --- a/source/source.py +++ b/source/source.py @@ -20,6 +20,13 @@ from manifest.manifest import build_manifest, save_manifest, review_manifest _ARCHIVE_DIR = "/tmp/docker-migrate-archives" +# Сервисы, которые НЕЛЬЗЯ останавливать/включать в архив — они управляют средой выполнения +SYSTEM_CRITICAL = { + "docker.service", "containerd.service", "docker.socket", + "systemd-journald.service", "systemd-networkd.service", + "systemd-timesyncd.service", "systemd-resolved.service", + "systemd-logind.service", "ssh.service", "sshd.service", +} def run_source_mode(): state.reset_state(mode="source") @@ -224,9 +231,16 @@ def do_pack(): details = s.get("details", {}) for f in details.get("files", []): if os.path.isfile(f): + # Пропускаем runtime/system пути — они не относятся к сервису + if any(f.startswith(p) for p in ( + "/proc/", "/sys/", "/dev/", + "/var/lib/containerd/", "/var/lib/docker/", + "/run/containerd/", "/run/docker/", + )): + continue files_to_pack.add(f) unit = details.get("unit") - if unit: + if unit and unit not in SYSTEM_CRITICAL: try: uout = run(f"systemctl show {unit} -p FragmentPath --value", check=False) path = uout.stdout.strip() @@ -237,6 +251,9 @@ def do_pack(): # systemd units for u in manifest.get("systemd_units", []): + uname = u.get("name") + if uname and uname in SYSTEM_CRITICAL: + continue p = u.get("path") if p and os.path.isfile(p): files_to_pack.add(p) @@ -290,14 +307,21 @@ def do_stop_service(): success(f"Контейнер {cid[:12]} остановлен") except Exception as e: warn(f"Не удалось остановить контейнер: {e}") + # Дедупликация + фильтр system-critical + stopped = set() for u in manifest.get("systemd_units", []): uname = u.get("name") - if uname: - try: - run(f"systemctl stop {uname}", check=False) - info(f"Остановлен systemd unit: {uname}") - except Exception: - pass + if not uname or uname in stopped: + continue + stopped.add(uname) + if uname in SYSTEM_CRITICAL: + warn(f"Пропуск system-critical unit: {uname}") + continue + try: + run(f"systemctl stop {uname}", check=False) + info(f"Остановлен systemd unit: {uname}") + except Exception: + pass else: info("Пропуск остановки (сервис продолжает работать)")