fix: install.sh pipe-mode, add .gitignore, robust error handling, resume state check
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+33
-7
@@ -97,14 +97,37 @@ def pretty_dict(data, indent=0):
|
||||
print(f"{prefix}{bold(k)}: {v}")
|
||||
|
||||
|
||||
def prompt(text):
|
||||
return input(f"{yellow('❯')} {text} ").strip()
|
||||
def prompt(text, default=None):
|
||||
"""Интерактивный ввод с обработкой EOF/pipe"""
|
||||
try:
|
||||
return input(f"{yellow('❯')} {text} ").strip()
|
||||
except EOFError:
|
||||
if default is not None:
|
||||
print(f" (pipe detected, используем default={default})")
|
||||
return default
|
||||
print(f"{red('✗')} Невозможно читать ввод (stdin закрыт). Перезапустите вне pipe.")
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{yellow('⚠')} Прервано пользователем (Ctrl+C)")
|
||||
sys.exit(130)
|
||||
|
||||
|
||||
def confirm(text, default="y"):
|
||||
"""Да/нет с обработкой EOF. В pipe — возвращает default."""
|
||||
yn = "Y/n" if default.lower() == "y" else "y/N"
|
||||
while True:
|
||||
r = input(f"{yellow('❯')} {text} [{yn}] ").strip().lower()
|
||||
try:
|
||||
r = input(f"{yellow('❯')} {text} [{yn}] ").strip().lower()
|
||||
except EOFError:
|
||||
if default.lower() == "y":
|
||||
print(f" (pipe detected, используем default={default})")
|
||||
return True
|
||||
else:
|
||||
print(f" (pipe detected, используем default={default})")
|
||||
return False
|
||||
except KeyboardInterrupt:
|
||||
print(f"\n{yellow('⚠')} Прервано пользователем (Ctrl+C)")
|
||||
sys.exit(130)
|
||||
if not r:
|
||||
r = default
|
||||
if r in ("y", "yes", "д", "да"):
|
||||
@@ -120,17 +143,20 @@ def divider():
|
||||
def banner():
|
||||
print(cyan(r"""
|
||||
╔══════════════════════════════════════════════════════════╗
|
||||
║ Docker Service Migration Tool ║
|
||||
║ Универсальный мастер переноса Docker-сервиса ║
|
||||
║ Docker Service Migration Tool ║
|
||||
║ Универсальный мастер переноса Docker-сервиса ║
|
||||
╚══════════════════════════════════════════════════════════╝
|
||||
"""))
|
||||
|
||||
|
||||
def menu():
|
||||
def menu(has_resume=False):
|
||||
print(f"\n{bold('Выберите режим:')}")
|
||||
print(f" {cyan('1')} {white('Подготовка к переносу (Source)')} — {gray('На текущем сервере')}")
|
||||
print(f" {cyan('2')} {white('Восстановление (Target)')} — {gray('На новом сервере')}")
|
||||
print(f" {cyan('3')} {white('Продолжить (Resume)')} — {gray('После исправления ошибки')}")
|
||||
if has_resume:
|
||||
print(f" {cyan('3')} {white('Продолжить (Resume)')} — {gray('После исправления ошибки')}")
|
||||
else:
|
||||
print(f" {gray('3')} {gray('Продолжить (Resume)')} — {gray('(нет сохранённого состояния)')}")
|
||||
print(f" {cyan('4')} {white('Статус и логи (Status/Logs)')} — {gray('Посмотреть состояние')}")
|
||||
print(f" {cyan('0')} {white('Выход')}")
|
||||
print()
|
||||
|
||||
+40
-9
@@ -7,12 +7,11 @@ import sys
|
||||
import os
|
||||
import argparse
|
||||
|
||||
# Добавляем корень проекта в PATH
|
||||
_PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
if _PROJECT_ROOT not in sys.path:
|
||||
sys.path.insert(0, _PROJECT_ROOT)
|
||||
|
||||
from core.color import banner, menu, prompt, info, error, warn
|
||||
from core.color import banner, menu, prompt, info, error as cerror, warn
|
||||
from core import state
|
||||
|
||||
|
||||
@@ -55,16 +54,42 @@ def main():
|
||||
# Интерактивный режим
|
||||
banner()
|
||||
while True:
|
||||
menu()
|
||||
choice = prompt("Ваш выбор").strip()
|
||||
st = state.load_state()
|
||||
has_resume = bool(st.get("mode") and st.get("stage") and st.get("stage") != "INIT")
|
||||
menu(has_resume=has_resume)
|
||||
try:
|
||||
choice = prompt("Ваш выбор", default="0").strip()
|
||||
except SystemExit:
|
||||
break
|
||||
if choice == "1":
|
||||
from source.source import run_source_mode
|
||||
run_source_mode()
|
||||
try:
|
||||
run_source_mode()
|
||||
except KeyboardInterrupt:
|
||||
warn("Прервано")
|
||||
except Exception as e:
|
||||
cerror(f"Ошибка: {e}")
|
||||
sys.exit(1)
|
||||
break
|
||||
elif choice == "2":
|
||||
from target.target import run_target_mode
|
||||
run_target_mode()
|
||||
try:
|
||||
run_target_mode()
|
||||
except KeyboardInterrupt:
|
||||
warn("Прервано")
|
||||
except Exception as e:
|
||||
cerror(f"Ошибка: {e}")
|
||||
sys.exit(1)
|
||||
break
|
||||
elif choice == "3":
|
||||
_do_resume()
|
||||
if not has_resume:
|
||||
warn("Нет сохранённого состояния для продолжения")
|
||||
continue
|
||||
try:
|
||||
_do_resume()
|
||||
except KeyboardInterrupt:
|
||||
warn("Прервано")
|
||||
break
|
||||
elif choice == "4":
|
||||
_do_status_logs()
|
||||
elif choice == "0":
|
||||
@@ -83,7 +108,10 @@ def _do_resume():
|
||||
info("Нет сохранённого состояния для продолжения")
|
||||
return
|
||||
fsm = FSM(mode=mode)
|
||||
fsm.resume_from(stage)
|
||||
try:
|
||||
fsm.resume_from(stage)
|
||||
except Exception as e:
|
||||
cerror(f"Ошибка при resume: {e}")
|
||||
|
||||
|
||||
def _do_status_logs():
|
||||
@@ -93,7 +121,10 @@ def _do_status_logs():
|
||||
print(" 1 Показать статус")
|
||||
print(" 2 Показать лог последней ошибки")
|
||||
print(" 0 Назад")
|
||||
c = prompt("Выбор").strip()
|
||||
try:
|
||||
c = prompt("Выбор", default="0").strip()
|
||||
except SystemExit:
|
||||
break
|
||||
if c == "1":
|
||||
stmod.show_status()
|
||||
elif c == "2":
|
||||
|
||||
Reference in New Issue
Block a user