Files
docker-migrate/transfer/transfer.py

171 lines
8.3 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) + удалённый запуск target-mode.
"""
import os
from core.color import info, success, warn, error as cerror, 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))
# --- Готовим 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_opts_common += f" -i '{key_path}'"
scp_opts_common += f" -i '{key_path}'"
info(f"Используем приватный SSH-ключ: {key_path}")
elif password:
# sshpass будет добавлен при вызове
info("Используем пароль через sshpass")
else:
ssh_opts_common += " -o BatchMode=yes"
scp_opts_common += " -o BatchMode=yes"
info("Пробуем ключевую аутентификацию (BatchMode)")
# Вспомогательная функция: выполнить SSH-команду (capture=True, для скриптов)
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)
# Интерактивный SSH (capture=False, для target-mode и другого интерактивного ввода)
def ssh_cmd_interactive(remote_cmd, timeout=300):
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, capture=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 = ssh_cmd(f"mkdir -p {remote_dir}")
if r0.returncode != 0:
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}")
# Выбираем метод
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":
info("Копируем архив через scp ...")
r = scp_cmd(archive_path, remote_path)
else:
info("Копируем архив через rsync ...")
r = rsync_cmd(archive_path, remote_path)
if r.returncode != 0:
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 = ssh_cmd(f"tar xzf {remote_path} -C {remote_dir}")
if r2.returncode != 0:
err = (r2.stderr or "").strip()[:200]
warn(f"Не удалось распаковать: {err}")
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 удалённо ...")
# 1. Упаковываем скрипт локально
project_local = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
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. Распаковываем и запускаем (интерактивно, т.к. target-mode спрашивает у пользователя)
ssh_cmd_interactive(f"mkdir -p /opt/docker-migrate-tool && tar xzf {tool_remote} -C /opt/docker-migrate-tool")
ssh_cmd_interactive(f"cd /opt/docker-migrate-tool && python3 core/main.py --mode=target --remote-dir={remote_dir}", timeout=600)
else:
success(f"Архив передан. Запустите на новом сервере: python3 core/main.py --mode=target --remote-dir={remote_dir}")
state.mark_completed("TRANSFER")
state.set_stage("DONE")