diff --git a/non_graf_installer/python-installer/oginstaller-v3.py b/non_graf_installer/python-installer/oginstaller-v3.py index 60f62ee..07e1e03 100644 --- a/non_graf_installer/python-installer/oginstaller-v3.py +++ b/non_graf_installer/python-installer/oginstaller-v3.py @@ -8,6 +8,7 @@ import threading # Importar threading para leer el log en tiempo real import socket import sys # Importar sys para leer los argumentos del script import logging # Importar el módulo logging +import shutil # Importar para verificar el tamaño del terminal CONFIGS_DIR = "/tmp/oginstall" LOGS_DIR = "/var/log/oginstaller" @@ -138,79 +139,208 @@ def get_oglive_list(): # Variable global para la IP por defecto DEFAULT_IP = get_default_ip() +class InstallationTypeForm(npyscreen.ActionForm): + """Formulario para seleccionar el tipo de instalación.""" + def create(self): + self.installation_type = self.add( + npyscreen.TitleSelectOne, + name="Selecciona el tipo de instalación:", + values=["Mononodo", "Multinodo"], + scroll_exit=False, max_height=10 + ) + + def on_ok(self): + """Guardar la selección y pasar al formulario correspondiente.""" + if self.installation_type.value is None: + npyscreen.notify_confirm("Debes seleccionar un tipo de instalación.", title="Error") + return + + if self.installation_type.value == [0]: # Mononodo + self.parentApp.installation_type = "mononodo" + self.parentApp.setNextForm("MONONODO_CONFIG") + elif self.installation_type.value == [1]: # Multinodo + self.parentApp.installation_type = "multinodo" + self.parentApp.setNextForm("MULTINODO_CONFIG") + + def on_cancel(self): + """Salir de la aplicación.""" + if npyscreen.notify_yes_no("¿Estás seguro de que deseas salir?", title="Confirmación"): + self.parentApp.setNextForm(None) + + +class MononodoConfigForm(npyscreen.ActionForm): + """Formulario para configurar Mononodo.""" + def create(self): + self.server_ip = self.add( + npyscreen.TitleText, + name="IP del servidor (mononodo):", + value=get_default_ip() + ) + + def on_ok(self): + """Guardar la configuración y pasar al siguiente formulario.""" + self.parentApp.server_ip = self.server_ip.value + self.parentApp.setNextForm("MAIN") + + def on_cancel(self): + """Volver al formulario de selección de tipo de instalación.""" + self.parentApp.setNextForm("INSTALLATION_TYPE") + + +class MultinodoConfigForm(npyscreen.ActionForm): + """Formulario para configurar Multinodo.""" + def create(self): + self.repo_ip = self.add( + npyscreen.TitleText, + name="IP del servidor Repository:", + value=get_default_ip() + ) + self.dhcp_ip = self.add( + npyscreen.TitleText, + name="IP del servidor DHCP:", + value=get_default_ip() + ) + self.core_ip = self.add( + npyscreen.TitleText, + name="IP del servidor Core:", + value=get_default_ip() + ) + self.boot_ip = self.add( + npyscreen.TitleText, + name="IP del servidor Boot:", + value=get_default_ip() + ) + + def on_ok(self): + """Guardar la configuración y pasar al siguiente formulario.""" + self.parentApp.repo_ip = self.repo_ip.value + self.parentApp.dhcp_ip = self.dhcp_ip.value + self.parentApp.core_ip = self.core_ip.value + self.parentApp.boot_ip = self.boot_ip.value + self.parentApp.setNextForm("MAIN") + + def on_cancel(self): + """Volver al formulario de selección de tipo de instalación.""" + self.parentApp.setNextForm("INSTALLATION_TYPE") + + class ComponentSelectionForm(npyscreen.ActionForm): def create(self): - self.components = self.add(npyscreen.TitleMultiSelect, max_height=6, name="Selecciona los componentes", - values=["ogCore", "ogGui", "ogDhcp", "ogBoot", "ogRepository"], scroll_exit=True) + self.components = self.add( + npyscreen.TitleMultiSelect, + max_height=6, + name="Selecciona los componentes", + values=["ogCore", "ogGui", "ogDhcp", "ogBoot", "ogRepository"], + scroll_exit=True + ) self.versions = get_available_versions() # Obtener las versiones desde el archivo JSON # Si no hay versiones disponibles, usar "latest" como opción por defecto if not self.versions: self.versions = ["latest"] - self.tag = self.add(npyscreen.TitleSelectOne, max_height=10, name="Selecciona la versión", - values=self.versions, scroll_exit=True) + self.tag = self.add( + npyscreen.TitleSelectOne, + max_height=10, + name="Selecciona la versión", + values=self.versions, + scroll_exit=True + ) self.tag.value = [0] # Marcar "latest" (o la primera opción) por defecto + # Mostrar la IP del servidor si es mononodo + if self.parentApp.installation_type == "mononodo": + self.server_ip = self.add( + npyscreen.TitleText, + name="IP del servidor (mononodo):", + value=self.parentApp.server_ip, + editable=False + ) + # Agregar un cuadro de texto para mostrar el log - self.log_box = self.add(npyscreen.BoxTitle, name="Log de depuración", max_height=10, scroll_exit=True) + self.log_box = self.add( + npyscreen.BoxTitle, + name="Log de depuración", + max_height=10, + scroll_exit=True + ) - def update_log(self): - """Actualiza el cuadro de texto con el contenido del archivo de log.""" - try: - with open(LOG_FILE, "r") as log_file: - lines = log_file.readlines() - self.log_box.values = lines[-self.log_box.height:] # Mostrar solo las últimas líneas - self.log_box.display() - except Exception as e: - logging.error(f"Error al leer el archivo de log: {e}") - - def while_waiting(self): - """Actualizar el log mientras se espera interacción del usuario.""" - self.update_log() + def beforeEditing(self): + """Configurar los valores iniciales de los componentes según el tipo de instalación.""" + if self.parentApp.installation_type == "mononodo": + # Seleccionar todos los componentes por defecto + self.components.value = list(range(len(self.components.values))) + else: + # No seleccionar ningún componente por defecto + self.components.value = [] + self.display() def on_ok(self): - npyscreen.blank_terminal() - selected_components = [self.components.values[i].lower() for i in self.components.value] # Convertir a minúsculas - if not selected_components: + # Validar selección obligatoria de componentes y versión + if not self.components.value or len(self.components.value) == 0: npyscreen.notify_confirm("Debes seleccionar al menos un componente.", title="Error") return - if not self.tag.value: + if not self.tag.value or len(self.tag.value) == 0: npyscreen.notify_confirm("Debes seleccionar una versión.", title="Error") return - selected_tag = self.versions[self.tag.value[0]] # Usar la versión seleccionada + + if self.parentApp.installation_type == "mononodo": + self.handle_mononodo() + else: + self.handle_multinodo() + + def handle_mononodo(self): + npyscreen.blank_terminal() + selected_components = [self.components.values[i].lower() for i in self.components.value] + selected_tag = self.versions[self.tag.value[0]] self.parentApp.selected_components = selected_components self.parentApp.selected_tag = selected_tag self.parentApp.current_component_index = 0 - self.parentApp.configurations = {} # Almacena los valores configurados - self.parentApp.switchForm(selected_components[0]) # Ya están en minúsculas + self.parentApp.configurations = {} + self.parentApp.switchForm(selected_components[0]) + + def handle_multinodo(self): + selected_components = [self.components.values[i].lower() for i in self.components.value] + selected_tag = self.versions[self.tag.value[0]] + self.parentApp.selected_components = selected_components + self.parentApp.selected_tag = selected_tag + self.parentApp.current_component_index = 0 + self.parentApp.configurations = {} + self.parentApp.switchForm(selected_components[0]) class ComponentForm(npyscreen.ActionForm): component_name = None def create(self): + # Agregar un título dinámico basado en el componente en la primera línea + self.title = self.add( + npyscreen.FixedText, + value="", + editable=False, + color="STANDOUT", + rely=0 # Forzar que el título esté en la primera línea + ) self.fields = {} def beforeEditing(self): npyscreen.blank_terminal() - self.fields.clear() + # Actualizar el valor del título dinámico basado en el componente + self.title.value = f"Configuración del componente: {self.component_name.upper()}" + self.title.display() self._recreate_form() def _recreate_form(self): - """Limpia y recrea los widgets del formulario.""" - self._clear_widgets() + """Limpia y recrea los widgets del formulario, excepto el título.""" + # No eliminar el título al recrear los widgets + self._widgets__ = [self.title] + self._widgets_by_id__ = {id(self.title): self.title} + self._contained_widgets = [self.title] self.configure_fields() def configure_fields(self): """Método para definir los campos de configuración para cada componente""" pass - def _clear_widgets(self): - """Limpia todos los widgets del formulario.""" - self._widgets__ = [] - self._widgets_by_id__ = {} - self._contained_widgets = [] - def on_ok(self): npyscreen.blank_terminal() component_config = {} @@ -236,57 +366,147 @@ class OgCoreForm(ComponentForm): component_name = "ogcore" def configure_fields(self): - self.fields["adminUser"] = {"widget": self.add(npyscreen.TitleText, name="Usuario administrador:", value="ogadmin")} - self.fields["adminPass"] = {"widget": self.add(npyscreen.TitlePassword, name="Contraseña:", value="12345678")} + + self.add(npyscreen.FixedText, value="Usuario Administrador: ", editable=False, rely=2, relx=2, color="SAFE" , highlighted=True) + self.fields["adminUser"] = { + "widget": self.add( + npyscreen.Textfield, + value="admin", + rely=3, # Línea siguiente + relx=18, + highlighted=True + ) + } + self.fields["adminPass"] = { + "widget": self.add( + npyscreen.TitlePassword, + name="Contraseña Administrador:", + value="12345678", + rely=6 , # Ajustar la posición vertical + highlighted=True , + scroll_exit=True + ) + } class OgGuiForm(ComponentForm): component_name = "oggui" def configure_fields(self): - self.fields["ogcoreUrl"] = {"widget": self.add(npyscreen.TitleText, name="URL API OgCore:", value="https://{}:8443".format(DEFAULT_IP))} - self.fields["ogmercureUrl"] = {"widget": self.add(npyscreen.TitleText, name="Mercure URL:", value="https://{}:3000/.well-known/mercure".format(DEFAULT_IP))} + """Configura los campos del formulario según el tipo de instalación.""" + if self.parentApp.installation_type == "mononodo": + self.server_ip = self.parentApp.server_ip + elif self.parentApp.installation_type == "multinodo": + self.server_ip = self.parentApp.core_ip + + self.add(npyscreen.FixedText, value="URL del servidor Core:", editable=False, rely=2, relx=2, color="SAFE" , highlighted=True) + self.fields["ogcoreUrl"] = { + "widget": self.add( + npyscreen.Textfield, + value=f"https://{self.server_ip}:8443", + rely=3 , + relx=18, + highlighted=True# Ajustar la posición vertical + ) + } + self.add(npyscreen.FixedText, value="URL del servidor Mercure:", editable=False, rely=4, relx=2, color="SAFE" , highlighted=True) + self.fields["ogmercureUrl"] = { + "widget": self.add( + npyscreen.Textfield, + value=f"https://{self.server_ip}:3000/.well-known/mercure", + rely=6, + relx=18# Ajustar la posición vertical + ) + } + + def on_ok(self): + """Guarda la configuración y pasa al siguiente formulario.""" + # Obtener la configuración del formulario + component_config = { + "ogcoreUrl": self.fields["ogcoreUrl"]["widget"].value, + "ogmercureUrl": self.fields["ogmercureUrl"]["widget"].value, + } + + # Guardar la configuración en el diccionario global + self.parentApp.configurations[self.component_name] = component_config + + # Continuar con el siguiente formulario + self.parentApp.current_component_index += 1 + if self.parentApp.current_component_index < len(self.parentApp.selected_components): + next_component = self.parentApp.selected_components[self.parentApp.current_component_index] + self.parentApp.switchForm(next_component) + else: + self.parentApp.generate_debconf() + self.parentApp.setNextForm(None) + class OgDhcpForm(ComponentForm): component_name = "ogdhcp" + def get_dhcp_ip(self): + """Obtiene la IP del servidor DHCP.""" + if self.parentApp.installation_type == "mononodo": + return self.parentApp.server_ip + elif self.parentApp.installation_type == "multinodo": + return self.parentApp.dhcp_ip + + def get_boot_ip(self): + """Obtiene la IP del servidor Boot.""" + if self.parentApp.installation_type == "mononodo": + return self.parentApp.server_ip + elif self.parentApp.installation_type == "multinodo": + return self.parentApp.boot_ip + def configure_fields(self): # Obtener las interfaces de red disponibles available_interfaces = get_network_interfaces().split(",") - # Mostrar las interfaces en una sola línea - interfaces_display = ", ".join(f"{i}:{iface}" for i, iface in enumerate(available_interfaces)) - - # Campo para mostrar las interfaces disponibles - self.fields["interfaces_display"] = { + # Selector de interfaces con altura ajustada + self.fields["dhcp_interfaces"] = { "widget": self.add( - npyscreen.TitleText, - name="Interfaces disponibles (índice:nombre):", - value=interfaces_display, - editable=False # Solo para mostrar, no editable + npyscreen.TitleMultiSelect, + name="Selecciona la interfaz de DHCP:", + values=available_interfaces, + scroll_exit=True, + rely=2, + max_height=5 # Reducir la altura para dejar espacio ) } - # Campo para que el usuario seleccione los índices de las interfaces - self.fields["interfaces"] = { + # Campo para la IP del servidor DHCP + self.fields["ip"] = { "widget": self.add( npyscreen.TitleText, - name="Selecciona los índices separados por comas:", - value="" + name="IP del servidor DHCP:", + value=self.get_dhcp_ip(), + rely=8 # Ajustar la posición vertical ) } - # Otros campos - self.fields["ip"] = {"widget": self.add(npyscreen.TitleText, name="IP del servidor DHCP:", value=DEFAULT_IP)} - self.fields["ogbootIP"] = {"widget": self.add(npyscreen.TitleText, name="IP del servidor Boot:", value=DEFAULT_IP)} + # Campo para la IP del servidor Boot + self.fields["ogbootIP"] = { + "widget": self.add( + npyscreen.TitleText, + name="IP del servidor Boot:", + value=self.get_boot_ip(), + rely=10 # Ajustar la posición vertical + ) + } def on_ok(self): - # Obtener los índices seleccionados por el usuario - selected_indices = self.fields["interfaces"]["widget"].value.split(",") + available_interfaces = self.fields["dhcp_interfaces"]["widget"].values + selected_indices = self.fields["dhcp_interfaces"]["widget"].value + # Validar que al menos un interfaz esté seleccionado + if not selected_indices or len(selected_indices) == 0: + npyscreen.notify_confirm("Debes seleccionar al menos una interfaz de red para DHCP.", title="Error") + return try: - # Convertir los índices en nombres de interfaces - available_interfaces = get_network_interfaces().split(",") - selected_interfaces = [available_interfaces[int(i.strip())] for i in selected_indices if i.strip().isdigit()] + for i in selected_indices: + if i < 0 or i >= len(available_interfaces): + raise IndexError("Índice fuera de rango") + selected_interfaces = [available_interfaces[i] for i in selected_indices] + logging.debug(f"Interfaces seleccionadas: {selected_interfaces}") interfaces_string = ",".join(selected_interfaces) + logging.debug(f"Interfaces seleccionadas: {interfaces_string}") except (IndexError, ValueError): npyscreen.notify_confirm("Selección inválida. Por favor, revisa los índices ingresados.", title="Error") return @@ -310,6 +530,20 @@ class OgDhcpForm(ComponentForm): class OgBootForm(ComponentForm): component_name = "ogboot" download_url = "https://ognproject.evlt.uma.es/oglive/" + def get_boot_ip(self): + """Obtiene la IP del servidor Boot.""" + if self.parentApp.installation_type == "mononodo": + return self.parentApp.server_ip + elif self.parentApp.installation_type == "multinodo": + return self.parentApp.boot_ip + + def get_core_ip(self): + """Obtiene la IP del servidor Core.""" + if self.parentApp.installation_type == "mononodo": + return self.parentApp.server_ip + elif self.parentApp.installation_type == "multinodo": + return self.parentApp.core_ip + def configure_fields(self): # Obtener la lista de oglives oglives = get_oglive_list() @@ -324,24 +558,73 @@ class OgBootForm(ComponentForm): name="Selecciona un OgLive:", values=oglives, scroll_exit=True, - max_height=10 # Limitar la altura para listas largas + max_height=6, + rely=2 # Ajustar la posición vertical ) } # Otros campos - self.fields["ip"] = {"widget": self.add(npyscreen.TitleText, name="IP del servidor Boot:", value=DEFAULT_IP)} - self.fields["port"] = {"widget": self.add(npyscreen.TitleText, name="Puerto Boot:", value="8082")} - self.fields["ogcoreUrl"] = {"widget": self.add(npyscreen.TitleText, name="URL OgCore:", value=f"https://{DEFAULT_IP}:8443")} - self.fields["sambaUser"] = {"widget": self.add(npyscreen.TitleText, name="Usuario Samba:", value="opengnsys")} - self.fields["sambaUserPass"] = {"widget": self.add(npyscreen.TitlePassword, name="Contraseña Samba:", value="og")} + #self.fields["ip"] = {"widget": self.add(npyscreen.TitleText, name="IP del servidor Boot:", value=self.get_boot_ip(), rely=14)} + self.add(npyscreen.FixedText, value="IP del servidor Boot:", editable=False, rely=10, relx=2, color="SAFE" , highlighted=True) + self.fields["ip"] = { + "widget": self.add( + npyscreen.Textfield, + value=self.get_boot_ip(), + rely=11, # Línea siguiente + relx=18, + highlighted=True + ) + } + # self.fields["ogcoreUrl"] = {"widget": self.add(npyscreen.TitleText, name="URL OgCore:", value=f"https://{self.get_core_ip()}:8443", rely=18)} + self.add(npyscreen.FixedText, value="IP del servidor Core:", editable=False, rely=12, relx=2, color="SAFE" , highlighted=True) + self.fields["ogcoreUrl"] = { + "widget": self.add( + npyscreen.Textfield, + value=f"https://{self.get_core_ip()}:8443", + rely=13, # Línea siguiente + relx=18, + highlighted=True + ) + } + # self.fields["sambaUser"] = {"widget": self.add(npyscreen.TitleText, name="Usuario Samba:", value="opengnsys", rely=20)} + self.add(npyscreen.FixedText, value="Usuario Samba:", editable=False, rely=14, relx=2, color="SAFE" , highlighted=True) + self.fields["sambaUser"] = { + "widget": self.add( + npyscreen.Textfield, + value="opengnsys", + rely=15, # Línea siguiente + relx=18, + highlighted=True + ) + } + #self.fields["sambaUserPass"] = {"widget": self.add(npyscreen.TitlePassword, name="Contraseña Samba:", value="og", rely=22)} + self.fields["sambaUserPass"] = { + "widget": self.add( + npyscreen.TitlePassword, + name="Contraseña Samba:", + value="og", + rely=16, # Línea siguiente + ) + } + #self.fields["port"] = {"widget": self.add(npyscreen.TitleText, name="Puerto Boot:", value="8082",hidden=True, rely=16)} + self.add(npyscreen.FixedText, value="Puerto Boot:", editable=False, rely=12, relx=2, color="SAFE" , highlighted=True,hidden=True) + self.fields["port"] = { + "widget": self.add( + npyscreen.Textfield, + value="8082", + rely=13, # Línea siguiente + relx=18, + hidden=True + ) + } def on_ok(self): # Obtener el oglive seleccionado selected_oglive_index = self.fields["ogliveUrl"]["widget"].value - if selected_oglive_index: - selected_oglive = self.fields["ogliveUrl"]["widget"].values[selected_oglive_index[0]] - else: - selected_oglive = None + if not selected_oglive_index or len(selected_oglive_index) == 0: + npyscreen.notify_confirm("Debes seleccionar una imagen OgLive.", title="Error") + return + selected_oglive = self.fields["ogliveUrl"]["widget"].values[selected_oglive_index[0]] # Guardar las configuraciones self.parentApp.configurations[self.component_name] = { @@ -365,28 +648,81 @@ class OgBootForm(ComponentForm): class OgRepositoryForm(ComponentForm): component_name = "ogrepository" + def get_repo_ip(self): + """Obtiene la IP del servidor Repository.""" + if self.parentApp.installation_type == "mononodo": + return self.parentApp.server_ip + elif self.parentApp.installation_type == "multinodo": + return self.parentApp.repo_ip + + def get_core_ip(self): + """Obtiene la IP del servidor Core.""" + if self.parentApp.installation_type == "mononodo": + return self.parentApp.server_ip + elif self.parentApp.installation_type == "multinodo": + return self.parentApp.core_ip + def configure_fields(self): - self.fields["ogrepoIp"] = {"widget": self.add(npyscreen.TitleText, name="IP del Repositorio:", value=DEFAULT_IP)} - self.fields["ogcoreIp"] = {"widget": self.add(npyscreen.TitleText, name="IP de OgCore:", value=DEFAULT_IP)} - self.fields["sambaUser"] = {"widget": self.add(npyscreen.TitleText, name="Usuario Samba:", value="opengnsys")} - self.fields["sambaUserPass"] = {"widget": self.add(npyscreen.TitlePassword, name="Contraseña Samba:", value="og")} + # Campo para la IP del Repositorio + self.add(npyscreen.FixedText, value="IP del Repositorio:", editable=False, rely=2, relx=2, color="SAFE" , highlighted=True) + self.fields["ogrepoIp"] = { + "widget": self.add( + npyscreen.Textfield, + value=self.get_repo_ip(), + rely=3, # Línea siguiente + relx=18, + highlighted=True + ) + } + + # Campo para la IP de OgCore + self.add(npyscreen.FixedText, value="IP de OgCore:", editable=False, rely=5, relx=2, color="SAFE" , highlighted=True) + self.fields["ogcoreIp"] = { + "widget": self.add( + npyscreen.Textfield, + value=self.get_core_ip(), + rely=6, # Línea siguiente + relx=18, + ) + } + + # Campo para el Usuario Samba + self.add(npyscreen.FixedText, value="Usuario Samba:", editable=False, rely=8, relx=2, color="SAFE" , highlighted=True) + self.fields["sambaUser"] = { + "widget": self.add( + npyscreen.Textfield, + value="opengnsys", + rely=9, # Línea siguiente + relx=18 , + ) + } + + # Campo para la Contraseña Samba + self.fields["sambaUserPass"] = { + "widget": self.add( + npyscreen.TitlePassword, + name="Contraseña Samba:", + value="og", + rely=11 # Mantener el uso de TitlePassword + ) + } class InstallationProgressForm(npyscreen.FormBaseNew): """Formulario para mostrar el progreso de instalación y el log en tiempo real.""" - def create(self): # Crear la parte superior para el progreso de instalación + def create(self): + # Usa tamaños fijos razonables que funcionen incluso en terminales pequeños self.progress_box = self.add( npyscreen.BoxTitle, name="Progreso de instalación", - max_height=int(self.lines * 0.5), # Mitad superior + max_height=10, scroll_exit=True ) - # Crear la parte inferior para el log en tiempo real self.log_box = self.add( npyscreen.BoxTitle, name="Log de instalación", - rely=int(self.lines * 0.5), # Mitad inferior + max_height=10, scroll_exit=True ) @@ -510,6 +846,21 @@ def install_components_with_ui(form, components, selected_tag): class MyApp(npyscreen.NPSAppManaged): def onStart(self): + # Inicializar variables globales + self.installation_type = None # Tipo de instalación seleccionado (mononodo o multinodo) + self.server_ip = None # IP del servidor para mononodo + self.repo_ip = None # IP del servidor Repository + self.dhcp_ip = None # IP del servidor DHCP + self.core_ip = None # IP del servidor Core + self.boot_ip = None # IP del servidor Boot + self.selected_components = [] # Componentes seleccionados + self.selected_tag = None # Versión seleccionada + self.configurations = {} # Configuraciones de los componentes + + # Registrar los formularios + self.addForm("INSTALLATION_TYPE", InstallationTypeForm) + self.addForm("MONONODO_CONFIG", MononodoConfigForm) + self.addForm("MULTINODO_CONFIG", MultinodoConfigForm) self.addForm("MAIN", ComponentSelectionForm) self.addForm("ogcore", OgCoreForm) self.addForm("oggui", OgGuiForm) @@ -518,6 +869,9 @@ class MyApp(npyscreen.NPSAppManaged): self.addForm("ogrepository", OgRepositoryForm) self.addForm("INSTALLATION_PROGRESS", InstallationProgressForm) + # Configurar el formulario inicial + self.setNextForm("INSTALLATION_TYPE") + def generate_debconf(self): # Comprobar si la clave pública ya existe key_path = "/etc/apt/trusted.gpg.d/opengnsys.gpg" @@ -593,8 +947,18 @@ class MyApp(npyscreen.NPSAppManaged): self.switchForm("INSTALLATION_PROGRESS") install_components_with_ui(form, self.selected_components, self.selected_tag) +def check_terminal_size(min_width=80, min_height=24): + """Verifica si el tamaño del terminal es suficiente.""" + terminal_size = shutil.get_terminal_size() + if terminal_size.columns < min_width or terminal_size.lines < min_height: + print(f"[ERROR] El tamaño del terminal es demasiado pequeño. Se requiere al menos {min_width}x{min_height}.") + exit(1) + if __name__ == "__main__": try: + # Verificar el tamaño del terminal antes de iniciar la aplicación + check_terminal_size() + logging.debug("Ejecutando la aplicación principal") MyApp().run() except RuntimeError as e: @@ -605,6 +969,7 @@ if __name__ == "__main__": logging.warning("El programa fue interrumpido por el usuario (Ctrl-C)") except Exception as e: logging.error(f"[ERROR] Ocurrió un error inesperado: {e}") + print(f"[ERROR] {e}") finally: # Asegurarse de que todos los mensajes de log se escriban en el archivo logging.debug("Finalizando el programa y cerrando el log")