diff --git a/transfer/transfer.py b/transfer/transfer.py index 9ffced7..a1b8abe 100644 --- a/transfer/transfer.py +++ b/transfer/transfer.py @@ -1,11 +1,10 @@ # -*- coding: utf-8 -*- """ -transfer.py — Адаптивный перенос архива (scp/rsync/fallback) +transfer.py — Адаптивный перенос архива (scp/rsync) + удалённый запуск target-mode. """ import os -import json -from core.color import info, success, warn, error as cerror, prompt, confirm, step +from core.color import info, success, warn, error as cerror, confirm, step from core import state from core.runner import run, exists @@ -42,25 +41,54 @@ def do_transfer(): remote_dir = "/tmp/docker-migrate-incoming" remote_path = os.path.join(remote_dir, os.path.basename(archive_path)) - # Создаём remote_dir через ssh перед scp/rsync - ssh_base = f"ssh -p {port} -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new" + # --- Готовим SSH/SCP опции один раз --- + ssh_opts_common = f"-p {port} -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new" + scp_opts_common = f"-P {port} -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new" + if key_path and os.path.isfile(key_path): - ssh_base += f" -i '{key_path}'" - info(f"Используем SSH-ключ: {key_path}") + ssh_opts_common += f" -i '{key_path}'" + scp_opts_common += f" -i '{key_path}'" + info(f"Используем приватный SSH-ключ: {key_path}") elif password: - if not exists("sshpass"): - raise RuntimeError("sshpass не найден, но нужен для пароля. Установите: apt-get install -y sshpass") - ssh_base = f"sshpass -p '{password}' {ssh_base}" + # sshpass будет добавлен при вызове info("Используем пароль через sshpass") else: - ssh_base += " -o BatchMode=yes" + ssh_opts_common += " -o BatchMode=yes" + scp_opts_common += " -o BatchMode=yes" info("Пробуем ключевую аутентификацию (BatchMode)") + # Вспомогательная функция: выполнить SSH-команду + def ssh_cmd(remote_cmd, timeout=30): + if password: + full = f"sshpass -p '{password}' ssh {ssh_opts_common} {user}@{host} '{remote_cmd}'" + else: + full = f"ssh {ssh_opts_common} {user}@{host} '{remote_cmd}'" + return run(full, check=False, timeout=timeout) + + # Вспомогательная функция: выполнить scp + def scp_cmd(src, dst, timeout=60): + if password: + full = f"sshpass -p '{password}' scp {scp_opts_common} {src} {user}@{host}:{dst}" + else: + full = f"scp {scp_opts_common} {src} {user}@{host}:{dst}" + return run(full, check=False, timeout=timeout) + + # Вспомогательная функция: rsync + def rsync_cmd(src, dst, timeout=60): + # rsync -e 'ssh ...' (без sshpass в -e, sshpass оборачивает всю команду) + ssh_part = ssh_opts_common + if password: + full = f"sshpass -p '{password}' rsync -avz --progress -e 'ssh {ssh_part}' {src} {user}@{host}:{dst}" + else: + full = f"rsync -avz --progress -e 'ssh {ssh_part}' {src} {user}@{host}:{dst}" + return run(full, check=False, timeout=timeout) + # Проверим что target отвечает (mkdir) info("Проверяем доступ к target ...") - r0 = run(f"{ssh_base} {user}@{host} 'mkdir -p {remote_dir}'", check=False, timeout=30) + r0 = ssh_cmd(f"mkdir -p {remote_dir}") if r0.returncode != 0: - cerror(f"SSH не удался: {r0.stderr.strip()[:200]}") + err = (r0.stderr or "").strip()[:200] + cerror(f"SSH не удался: {err}") state.set_error("TRANSFER", r0.stdout, r0.stderr, suggestion="Проверьте пароль/ключ и доступ SSH. После исправления: docker-migrate --resume") raise RuntimeError(f"SSH-доступ не работает. Код: {r0.returncode}") @@ -79,44 +107,28 @@ def do_transfer(): info(f"Выбран метод переноса: {method}") + # Перенос архива if method == "scp": - # scp: заглавная -P для порта - scp_opts = f"-P {port} -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new" - if key_path and os.path.isfile(key_path): - scp_opts += f" -i '{key_path}'" - info("Копируем архив через scp ...") - r = run(f"scp {scp_opts} {archive_path} {user}@{host}:{remote_path}", check=False, timeout=60) - elif password: - # sshpass оборачивает всю команду scp - info("Копируем архив через scp + sshpass ...") - r = run(f"sshpass -p '{password}' scp {scp_opts} {archive_path} {user}@{host}:{remote_path}", check=False, timeout=60) - else: - scp_opts += " -o BatchMode=yes" - info("Копируем архив через scp ...") - r = run(f"scp {scp_opts} {archive_path} {user}@{host}:{remote_path}", check=False, timeout=60) + info("Копируем архив через scp ...") + r = scp_cmd(archive_path, remote_path) else: - # rsync через ssh_base info("Копируем архив через rsync ...") - rsync_ssh = ssh_base.replace("ssh ", "") # убираем 'ssh ' для rsync -e - r = run(f"rsync -avz --progress -e 'ssh {rsync_ssh}' {archive_path} {user}@{host}:{remote_path}", check=False, timeout=60) + r = rsync_cmd(archive_path, remote_path) if r.returncode != 0: - cerror(f"Передача не удалась: {r.stderr.strip()[:200]}") - state.set_error( - step="TRANSFER", - stdout=r.stdout, - stderr=r.stderr, - suggestion=f"Проверьте SSH-доступ к {user}@{host}:{port}. После исправления: docker-migrate --resume" - ) + err = (r.stderr or "").strip()[:200] + cerror(f"Передача не удалась: {err}") + 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_base} {user}@{host} 'tar xzf {remote_path} -C {remote_dir}'", check=False, timeout=60) + r2 = ssh_cmd(f"tar xzf {remote_path} -C {remote_dir}") if r2.returncode != 0: - warn(f"Не удалось распаковать: {r2.stderr.strip()[:200]}") + err = (r2.stderr or "").strip()[:200] + warn(f"Не удалось распаковать: {err}") state.set_error("TRANSFER_UNPACK", r2.stdout, r2.stderr, suggestion="Проверьте tar на target") raise RuntimeError("Распаковка не удалась") @@ -126,11 +138,25 @@ def do_transfer(): # Предлагаем запустить target-режим удалённо if confirm("Сразу запустить восстановление на новом сервере", default="y"): info("Запускаем target-mode удалённо ...") + # 1. Упаковываем скрипт локально project_local = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - run(f"{ssh_base} {user}@{host} 'mkdir -p /opt/docker-migrate-tool && tar czf - -C {project_local} . | tar xzf - -C /opt/docker-migrate-tool'", check=False, timeout=60) - run(f"{ssh_base} {user}@{host} 'cd /opt/docker-migrate-tool && python3 core/main.py --mode=target --remote-dir={remote_dir}'", check=False, timeout=300) + local_tool_tar = "/tmp/docker-migrate-tool.tar.gz" + run(f"tar czf {local_tool_tar} -C {project_local} .", check=False, timeout=30) + # 2. Передаём на новый сервер + tool_remote = "/tmp/docker-migrate-tool.tar.gz" + if method == "scp": + r3 = scp_cmd(local_tool_tar, tool_remote) + else: + r3 = rsync_cmd(local_tool_tar, tool_remote) + if r3.returncode != 0: + warn("Не удалось передать скрипт на новый сервер.") + success(f"Архив передан. Запустите на новом сервере: python3 core/main.py --mode=target --remote-dir={remote_dir}") + else: + # 3. Распаковываем и запускаем + ssh_cmd(f"mkdir -p /opt/docker-migrate-tool && tar xzf {tool_remote} -C /opt/docker-migrate-tool") + ssh_cmd(f"cd /opt/docker-migrate-tool && python3 core/main.py --mode=target --remote-dir={remote_dir}", timeout=300) else: - success(f"Архив передан. Запустите на target: python3 core/main.py --mode=target --remote-dir={remote_dir}") + success(f"Архив передан. Запустите на новом сервере: python3 core/main.py --mode=target --remote-dir={remote_dir}") state.mark_completed("TRANSFER") state.set_stage("DONE")