diff --git a/non_graf_installer/python-installer/oginstaller-v3.py b/non_graf_installer/python-installer/oginstaller-v3.py index 76a6685..4661895 100644 --- a/non_graf_installer/python-installer/oginstaller-v3.py +++ b/non_graf_installer/python-installer/oginstaller-v3.py @@ -3,7 +3,6 @@ import os from git import Repo import subprocess # Importar el módulo subprocess import requests # Importar el módulo requests -from tqdm import tqdm # Importar tqdm para la barra de progreso import time # Importar time para simular el progreso import threading # Importar threading para leer el log en tiempo real import socket @@ -64,6 +63,25 @@ def get_default_ip(): print(f"Error al obtener la IP por defecto: {e}") return "192.168.2.2" # Valor por defecto +def get_oglive_list(): + """Obtiene la lista de valores de oglives desde la URL.""" + try: + # Realizar la solicitud HTTP + response = requests.get("https://ognproject.evlt.uma.es/oglive/", timeout=10) + response.raise_for_status() # Lanza una excepción si la respuesta no es 200 OK + + # Extraer los enlaces del contenido HTML + from bs4 import BeautifulSoup + soup = BeautifulSoup(response.text, "html.parser") + links = [a["href"] for a in soup.find_all("a", href=True) if "ogLive" in a["href"]] + + # Ordenar los enlaces por la parte después del guion bajo + sorted_links = sorted(links, key=lambda x: x.split("_")[1] if "_" in x else x, reverse=True) + return sorted_links + except Exception as e: + print(f"Error al obtener la lista de oglives: {e}") + return [] # Devolver una lista vacía en caso de error + # Variable global para la IP por defecto DEFAULT_IP = get_default_ip() @@ -150,7 +168,7 @@ class OgGuiForm(ComponentForm): 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://{} gr:3000/.well-known/mercure".format(DEFAULT_IP))} + self.fields["ogmercureUrl"] = {"widget": self.add(npyscreen.TitleText, name="Mercure URL:", value="https://{}:3000/.well-known/mercure".format(DEFAULT_IP))} class OgDhcpForm(ComponentForm): component_name = "ogdhcp" @@ -217,13 +235,57 @@ class OgBootForm(ComponentForm): component_name = "ogboot" def configure_fields(self): + # Obtener la lista de oglives + oglives = get_oglive_list() + if not oglives: + oglives = ["https://ognproject.evlt.uma.es/oglive/ogLive-noble-6.8.0-31-generic-amd64-r20250116.538e3fa_20250120.iso"] + npyscreen.notify_confirm("No se pudo obtener la lista de oglives. Usando un valor por defecto.", title="Error") + + # Campo para seleccionar un oglive + self.fields["ogliveUrl"] = { + "widget": self.add( + npyscreen.TitleSelectOne, + name="Selecciona un OgLive:", + values=oglives, + scroll_exit=True, + max_height=10 # Limitar la altura para listas largas + ) + } + + # 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["ogliveUrl"] = {"widget": self.add(npyscreen.TitleText, name="URL OgLive:", value="https://ognproject.evlt.uma.es/oglive/ogLive-noble-6.8.0-31-generic-amd64-r20250116.538e3fa_20250120.iso")} 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")} + 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 + + # Guardar las configuraciones + self.parentApp.configurations[self.component_name] = { + "ogliveUrl": selected_oglive, + "ip": self.fields["ip"]["widget"].value, + "port": self.fields["port"]["widget"].value, + "ogcoreUrl": self.fields["ogcoreUrl"]["widget"].value, + "sambaUser": self.fields["sambaUser"]["widget"].value, + "sambaUserPass": self.fields["sambaUserPass"]["widget"].value, + } + + # 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 OgRepositoryForm(ComponentForm): component_name = "ogrepository" @@ -233,8 +295,50 @@ class OgRepositoryForm(ComponentForm): 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")} -def install_components(components, selected_tag): - """Instala los componentes seleccionados usando el tag especificado.""" +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 + self.progress_box = self.add( + npyscreen.BoxTitle, + name="Progreso de instalación", + max_height=int(self.lines * 0.5), # Mitad superior + scroll_exit=True + ) + + # Crear la parte inferior para el log en tiempo real + self.log_box = self.add( + npyscreen.BoxTitle, + name="Log en tiempo real", + rely=int(self.lines * 0.5), # Mitad inferior + scroll_exit=True + ) + + def update_progress(self, message, current=0, total=0): + """Actualiza el progreso de instalación en la parte superior.""" + if total > 0: + # Crear una barra de progreso personalizada + progress_percentage = int((current / total) * 100) + bar_length = 30 # Longitud de la barra + filled_length = int(bar_length * current // total) + bar = f"[{'=' * filled_length}{' ' * (bar_length - filled_length)}] {progress_percentage}%" + self.progress_box.values.append(bar) + self.progress_box.values.append(message) + self.progress_box.display() + + def update_log(self, log_lines): + """Actualiza el log en tiempo real en la parte inferior.""" + # Limpiar caracteres especiales de las líneas del log + cleaned_lines = [self._clean_text(line) for line in log_lines] + self.log_box.values = cleaned_lines[-self.log_box.height:] # Mostrar solo las últimas líneas + self.log_box.display() + + def _clean_text(self, text): + """Elimina caracteres especiales o no imprimibles del texto.""" + return ''.join(c if c.isprintable() else '?' for c in text) + +def install_components_with_ui(form, components, selected_tag): + """Instala los componentes seleccionados mostrando el progreso y el log en tiempo real.""" log_file_path = os.path.join(CONFIGS_DIR, "installation.log") installed_packages = [] # Lista de paquetes instalados correctamente failed_packages = [] # Lista de paquetes que fallaron @@ -245,61 +349,44 @@ def install_components(components, selected_tag): try: with open(log_file_path, "w") as log_file: total_packages = len(components) + + # Hilo para leer el log en tiempo real + def tail_log(): + with open(log_file_path, "r") as log_reader: + log_reader.seek(0, os.SEEK_END) # Ir al final del archivo + while True: + line = log_reader.readline() + if line: + form.update_log(log_reader.readlines()) + time.sleep(0.1) + + log_thread = threading.Thread(target=tail_log, daemon=True) + log_thread.start() + for index, package in enumerate(components, start=1): - # Mostrar solo el progreso en la terminal - print(f"Instalando paquete {index}/{total_packages}: {package}") - log_file.write(f"\n--- Instalando paquete {index}/{total_packages}: {package} ---\n") - + # Actualizar el progreso en la parte superior con barra de progreso + form.update_progress(f"Instalando paquete {index}/{total_packages}: {package}", current=index, total=total_packages) + # Crear una barra de progreso para el paquete - with tqdm(total=100, desc=f"Instalando {package}", unit="%", ncols=80) as progress_bar: - # Ejecutar el comando y redirigir toda la salida al archivo de registro - install_command = f"DEBIAN_FRONTEND=noninteractive apt-get install -y {package}" - process = subprocess.Popen( - install_command, shell=True, text=True, stdout=log_file, stderr=log_file - ) + install_command = f"DEBIAN_FRONTEND=noninteractive apt-get install -y {package}" + process = subprocess.Popen( + install_command, shell=True, text=True, stdout=log_file, stderr=log_file + ) - # Función para leer el log en tiempo real y actualizar la barra de progreso - def monitor_log(): - with open(log_file_path, "r") as log_reader: - log_reader.seek(0, os.SEEK_END) # Ir al final del archivo - while process.poll() is None: - line = log_reader.readline() - if not line: - time.sleep(0.1) # Esperar si no hay nuevas líneas - continue - # Actualizar la barra de progreso según las cadenas detectadas - if "Se necesita descargar" in line: - progress_bar.n = 10 - elif "Preparando para desempaquetar" in line: - progress_bar.n = 30 - elif "Configurando" in line and package in line: - progress_bar.n = 40 - progress_bar.refresh() + # Esperar a que el proceso de instalación termine + process.wait() - # Iniciar el hilo para monitorear el log - log_thread = threading.Thread(target=monitor_log, daemon=True) - log_thread.start() - - # Esperar a que el proceso de instalación termine - process.wait() - - # Completar la barra de progreso - progress_bar.n = 100 - progress_bar.refresh() - - # Registrar errores en el archivo de registro - if process.returncode != 0: - error_message = f"Error al instalar el paquete {package}. Consulta el archivo de registro: {log_file_path}" - print(error_message) - log_file.write(f"\n{error_message}\n") - failed_packages.append(package) # Agregar a la lista de fallos - else: - log_file.write(f"Paquete {package} instalado correctamente.\n") - installed_packages.append(package) # Agregar a la lista de éxitos + # Registrar errores en el archivo de registro + if process.returncode != 0: + error_message = f"Error al instalar el paquete {package}. Consulta el archivo de registro: {log_file_path}" + form.update_progress(error_message) + failed_packages.append(package) # Agregar a la lista de fallos + else: + form.update_progress(f"Paquete {package} instalado correctamente.") + installed_packages.append(package) # Agregar a la lista de éxitos except Exception as e: - with open(log_file_path, "a") as log_file: - log_file.write(f"\nError durante la instalación de los paquetes: {e}\n") - failed_packages.append("Error general durante la instalación") + form.update_progress(f"Error durante la instalación: {e}") + failed_packages.append("Error general durante la instalación") # Registrar el tiempo de finalización end_time = time.time() @@ -322,6 +409,9 @@ def install_components(components, selected_tag): # Mostrar el resumen en una ventana emergente npyscreen.notify_confirm(summary, title="Resumen de la instalación", wide=True) + # Mostrar el resumen en la parte superior (opcional, si quieres mantenerlo en el formulario) + form.update_progress(summary) + class MyApp(npyscreen.NPSAppManaged): def onStart(self): self.addForm("MAIN", ComponentSelectionForm) @@ -330,6 +420,7 @@ class MyApp(npyscreen.NPSAppManaged): self.addForm("ogdhcp", OgDhcpForm) self.addForm("ogboot", OgBootForm) self.addForm("ogrepository", OgRepositoryForm) + self.addForm("INSTALLATION_PROGRESS", InstallationProgressForm) def generate_debconf(self): # Comprobar si la clave pública ya existe @@ -387,8 +478,10 @@ class MyApp(npyscreen.NPSAppManaged): # Silenciar el mensaje de configuraciones guardadas # print(f"\nConfiguraciones guardadas en: {output_file}") - # Llamar a la función de instalación después de generar las configuraciones - install_components(self.selected_components, self.selected_tag) + # Llamar al formulario de progreso + form = self.getForm("INSTALLATION_PROGRESS") + self.switchForm("INSTALLATION_PROGRESS") + install_components_with_ui(form, self.selected_components, self.selected_tag) if __name__ == "__main__": MyApp().run() diff --git a/non_graf_installer/python-installer/requirements.txt b/non_graf_installer/python-installer/requirements.txt index 7d7c972..9c97564 100644 --- a/non_graf_installer/python-installer/requirements.txt +++ b/non_graf_installer/python-installer/requirements.txt @@ -1,4 +1,4 @@ GitPython npyscreen requests -tqdm +bs4