Files
docker-migrate/transfer/transfer.py

137 lines
6.6 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)
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")