@@ -19,6 +19,8 @@ def do_transfer():
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 не указан. Перенос невозможен. " )
@@ -36,14 +38,31 @@ def do_transfer():
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 ) )
# Копируем bootstrap (migrate + core/) чтобы target мог запуститься
# Найдём корень проекта
project_root = os . path . dirname ( os . path . dirname ( os . path . abspath ( __file__ ) ) )
tar_script = os . path . join ( project_root , " tar_project.sh " )
# Создаём 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 " ) :
@@ -60,28 +79,29 @@ def do_transfer():
info ( f " Выбран метод переноса: { method } " )
key_path = st . get ( " ssh_key " )
# Предварительная проверка уже сделана в pick_or_setup_ssh_key, повторно не делаем
# Базовые SSH-опции (ssh/rsh/rsync)
ssh_opts = f " -p { port } "
if key_path and os . path . isfile ( key_path ) :
ssh_opts + = f " -i ' { key_path } ' "
info ( f " Используем SSH-ключ: { key_path } " )
if method == " scp " :
# scp использует заглавную -P для порта (строчная -p = preserve timestamps)
# Добавляем SSH-опции чтобы избежать зависания и silent fail
scp_opts = f " -P { port } -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -o BatchMode=yes "
# 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 ... " )
r = run ( f " rsync -avz --progress -e ' ssh { ssh_opts } ' { archive_path } { user } @ { host } : { remote_path } " , check = False , timeout = 60 )
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 ,
@@ -92,33 +112,25 @@ def do_transfer():
success ( f " Архив передан на { host } : { remote_path } " )
# Распаковка на target во временную директорию
# Распаковка на target
info ( " Распаковываем архив на target ... " )
r2 = run ( f " ssh { ssh_opts } { user } @ { host } ' mkdir -p { remote_dir } && tar xzf { remote_path } -C { remote_dir } ' " , check = False )
r2 = run ( f " { ssh_base } { user } @ { host } ' tar xzf { remote_path } -C { remote_dir } ' " , check = False , timeout = 60 )
if r2 . returncode != 0 :
warn ( f " Н е удалось распаковать архив на target : { r2 . stderr } " )
state . set_error (
step = " TRANSFER_UNPACK " ,
stdout = r2 . stdout ,
stderr = r2 . stderr ,
suggestion = f " Проверьте доступ SSH и tar на { host } . После исправления: docker-migrate --resume "
)
raise RuntimeError ( f " Распаковка на target не удалась: { r2 . stderr } " )
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 ( " Сразу запустить восстановление на новом сервере (remote target mode) " , default = " y " ) :
# Предлагаем запустить target-режим удалённо
if confirm ( " Сразу запустить восстановление на новом сервере " , default = " y " ) :
info ( " Запускаем target-mode удалённо ... " )
# Передаём скрипт на target
project_local = os . path . dirname ( os . path . dirname ( os . path . abspath ( __file__ ) ) )
run ( f " scp { ssh_opts } -r { project_local } { user } @ { host } : /opt/docker-migrate-tool" , check = False )
# Запуск target как remote python
run ( f " ssh { ssh_opts } { user } @ { host } ' cd /opt/docker-migrate-tool && python3 core/main.py --mode=target --remote-dir= { remote_dir } ' " , check = False )
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 } " )
# Transfer выполнен — отмечаем выполненным, чтобы resume не повторял
state . mark_completed ( " TRANSFER " )
state . set_stage ( " DONE " )