fix: install.sh pipe-mode, add .gitignore, robust error handling, resume state check

This commit is contained in:
2026-05-22 20:08:41 +04:00
parent ddb2ae9501
commit 13a1583df1
17 changed files with 210 additions and 76 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+33 -7
View File
@@ -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
View File
@@ -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":