Files
docker-migrate/transfer/transfer.py

130 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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}")
key_path = st.get("ssh_key")
# Предварительная проверка уже сделана в pick_or_setup_ssh_key, повторно не делаем
# Базовые SSH-опции (ssh/rsh/rsync)
ssh_opts = f"-p {port}"
if key_path and os.path.isfile(key_path):
ssh_opts += f" -i '{key_path}'"
info(f"Используем SSH-ключ: {key_path}")
if method == "scp":
# scp использует заглавную -P для порта (строчная -p = preserve timestamps)
# Добавляем SSH-опции чтобы избежать зависания и silent fail
if not (key_path and os.path.isfile(key_path)):
warn("SSH-ключ не найден. scp с BatchMode=yes не может использовать пароль.")
info("Варианты: 1) Сгенерируйте ключ через resume; 2) Используйте rsync; 3) Перенесите вручную.")
state.set_error("TRANSFER", "", "SSH key missing for scp with BatchMode", suggestion="Запустите docker-migrate --resume и сгенерируйте временный SSH-ключ")
raise RuntimeError("SSH-ключ отсутствует. Автоматический перенос невозможен без ключа.")
scp_opts = f"-P {port} -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -o BatchMode=yes"
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)
else:
info("Копируем архив через rsync ...")
r = run(f"rsync -avz --progress -e 'ssh {ssh_opts}' {archive_path} {user}@{host}:{remote_path}", check=False, timeout=60)
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")