# -*- coding: utf-8 -*- """ transfer.py — Адаптивный перенос архива (scp/rsync/fallback) """ import os import json from core.color import info, success, warn, error as cerror, prompt, confirm, step from core import state from core.runner import run, exists def do_transfer(): st = state.load_state() archive_path = st.get("archive_path") if not archive_path or not os.path.isfile(archive_path): raise RuntimeError("Архив не найден. Сначала выполните сборку (Source → Pack).") host = st.get("target_host") user = st.get("target_user") port = st.get("target_port", 22) if not host or host.lower() in ("none", "", "localhost"): warn("Target host не указан. Перенос невозможен.") state.set_stage("TRANSFER_SKIPPED") return if not user or user.lower() == "none": user = "root" warn(f"SSH user не указан, используем default: {user}") step(5, "ПЕРЕНОС АРХИВА") info(f"Target: {user}@{host}:{port}") # Определяем размер size = os.path.getsize(archive_path) size_mb = size / 1024 / 1024 info(f"Размер архива: {size_mb:.2f} MB") # Определяем путь назначения remote_dir = "/tmp/docker-migrate-incoming" remote_path = os.path.join(remote_dir, os.path.basename(archive_path)) # Копируем bootstrap (migrate + core/) чтобы target мог запуститься # Найдём корень проекта project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) tar_script = os.path.join(project_root, "tar_project.sh") # Выбираем метод if size_mb < 50 and exists("scp"): method = "scp" elif size_mb >= 50 and exists("rsync"): method = "rsync" else: if exists("rsync"): method = "rsync" elif exists("scp"): method = "scp" else: raise RuntimeError("Ни rsync, ни scp не найдены. Установите один из них.") info(f"Выбран метод переноса: {method}") ssh_opts = f"-p {port}" key_path = st.get("ssh_key") if key_path and os.path.isfile(key_path): ssh_opts += f" -i '{key_path}'" info(f"Используем SSH-ключ: {key_path}") # Предварительная проверка уже сделана в pick_or_setup_ssh_key, повторно не делаем if method == "scp": info("Копируем архив через scp ...") r = run(f"scp {ssh_opts} {archive_path} {user}@{host}:{remote_path}", check=False) else: info("Копируем архив через rsync ...") r = run(f"rsync -avz --progress -e 'ssh {ssh_opts}' {archive_path} {user}@{host}:{remote_path}", check=False) if r.returncode != 0: state.set_error( step="TRANSFER", stdout=r.stdout, stderr=r.stderr, suggestion=f"Проверьте SSH-доступ к {user}@{host}:{port}. После исправления: docker-migrate --resume" ) raise RuntimeError(f"Передача файла не удалась. Код возврата: {r.returncode}") success(f"Архив передан на {host}:{remote_path}") # Распаковка на target во временную директорию info("Распаковываем архив на target ...") r2 = run(f"ssh {ssh_opts} {user}@{host} 'mkdir -p {remote_dir} && tar xzf {remote_path} -C {remote_dir}'", check=False) if r2.returncode != 0: warn(f"Не удалось распаковать архив на target: {r2.stderr}") state.set_error( step="TRANSFER_UNPACK", stdout=r2.stdout, stderr=r2.stderr, suggestion=f"Проверьте доступ SSH и tar на {host}. После исправления: docker-migrate --resume" ) raise RuntimeError(f"Распаковка на target не удалась: {r2.stderr}") # Сохраняем remote_dir в state для target state.set_stage("TRANSFER", target_remote_dir=remote_dir) # Предлагаем сразу запустить target-режим удалённо if confirm("Сразу запустить восстановление на новом сервере (remote target mode)", default="y"): info("Запускаем target-mode удалённо ...") # Передаём скрипт на target project_local = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) run(f"scp {ssh_opts} -r {project_local} {user}@{host}:/opt/docker-migrate-tool", check=False) # Запуск target как remote python run(f"ssh {ssh_opts} {user}@{host} 'cd /opt/docker-migrate-tool && python3 core/main.py --mode=target --remote-dir={remote_dir}'", check=False) else: success(f"Архив передан. Запустите на target: python3 core/main.py --mode=target --remote-dir={remote_dir}") # Transfer выполнен — отмечаем выполненным, чтобы resume не повторял state.mark_completed("TRANSFER") state.set_stage("DONE")