Files
docker-migrate/transfer/transfer.py
T

137 lines
6.6 KiB
Python

# -*- 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)
key_path = st.get("ssh_key")
password = st.get("ssh_password")
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))
# Создаём remote_dir через ssh перед scp/rsync
ssh_base = f"ssh -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}")
elif password:
if not exists("sshpass"):
raise RuntimeError("sshpass не найден, но нужен для пароля. Установите: apt-get install -y sshpass")
ssh_base = f"sshpass -p '{password}' {ssh_base}"
info("Используем пароль через sshpass")
else:
ssh_base += " -o BatchMode=yes"
info("Пробуем ключевую аутентификацию (BatchMode)")
# Проверим что target отвечает (mkdir)
info("Проверяем доступ к target ...")
r0 = run(f"{ssh_base} {user}@{host} 'mkdir -p {remote_dir}'", check=False, timeout=30)
if r0.returncode != 0:
cerror(f"SSH не удался: {r0.stderr.strip()[:200]}")
state.set_error("TRANSFER", r0.stdout, r0.stderr, suggestion="Проверьте пароль/ключ и доступ SSH. После исправления: docker-migrate --resume")
raise RuntimeError(f"SSH-доступ не работает. Код: {r0.returncode}")
# Выбираем метод
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}")
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)
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)
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"
)
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)
if r2.returncode != 0:
warn(f"Не удалось распаковать: {r2.stderr.strip()[:200]}")
state.set_error("TRANSFER_UNPACK", r2.stdout, r2.stderr, suggestion="Проверьте tar на target")
raise RuntimeError("Распаковка не удалась")
# Сохраняем remote_dir в state для target
state.set_stage("TRANSFER", target_remote_dir=remote_dir)
# Предлагаем запустить target-режим удалённо
if confirm("Сразу запустить восстановление на новом сервере", default="y"):
info("Запускаем target-mode удалённо ...")
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)
else:
success(f"Архив передан. Запустите на target: python3 core/main.py --mode=target --remote-dir={remote_dir}")
state.mark_completed("TRANSFER")
state.set_stage("DONE")