fix: transfer.py — helper functions for ssh/scp/rsync, local tar then remote unpack for tool script, consistent error handling

This commit is contained in:
2026-05-23 00:11:04 +04:00
parent bfd6302448
commit 80bb196d24

View File

@@ -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")