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:
@@ -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)
|
||||
r = scp_cmd(archive_path, remote_path)
|
||||
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)
|
||||
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:
|
||||
success(f"Архив передан. Запустите на target: python3 core/main.py --mode=target --remote-dir={remote_dir}")
|
||||
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"Архив передан. Запустите на новом сервере: python3 core/main.py --mode=target --remote-dir={remote_dir}")
|
||||
|
||||
state.mark_completed("TRANSFER")
|
||||
state.set_stage("DONE")
|
||||
|
||||
Reference in New Issue
Block a user