From 92e24117e08ff3ce25f8714267f131563813f8ad Mon Sep 17 00:00:00 2001 From: Nicolas Arenas Date: Fri, 21 Mar 2025 18:48:13 +0100 Subject: [PATCH] First installer version for debian packages --- .../python-installer/oginstaller-v3.py | 302 ++++++++++++++++++ .../python-installer/requirements.txt | 2 + 2 files changed, 304 insertions(+) create mode 100644 non_graf_installer/python-installer/oginstaller-v3.py diff --git a/non_graf_installer/python-installer/oginstaller-v3.py b/non_graf_installer/python-installer/oginstaller-v3.py new file mode 100644 index 0000000..e5ba339 --- /dev/null +++ b/non_graf_installer/python-installer/oginstaller-v3.py @@ -0,0 +1,302 @@ +import npyscreen +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 + +CONFIGS_DIR = "/tmp/oginstall" +os.makedirs(CONFIGS_DIR, exist_ok=True) + +REPO_URL = "https://ognproject.evlt.uma.es/gitea/opengnsys/ogcore.git" + + +def get_network_interfaces(): + """Obtiene los nombres de las interfaces de red disponibles en el servidor.""" + try: + # Listar las interfaces de red desde /sys/class/net + interfaces = os.listdir('/sys/class/net') + # Filtrar interfaces válidas (excluyendo interfaces virtuales como 'lo') + valid_interfaces = [iface for iface in interfaces if not iface.startswith('lo')] + return ','.join(valid_interfaces) # Devuelve las interfaces separadas por comas + except Exception as e: + # En caso de error, devolver un valor por defecto + print(f"Error al obtener las interfaces de red: {e}") + return "eth0" # Valor por defecto + +def get_available_versions(): + """Obtiene la lista de versiones desde el archivo JSON remoto.""" + try: + url = "https://ognproject.evlt.uma.es/debian-opengnsys/versions.json" + # Redirigir la salida de la descarga a /dev/null + response = requests.get(url, timeout=10) + response.raise_for_status() # Lanza una excepción si la respuesta no es 200 OK + data = response.json() + return data.get("versions", []) + except requests.RequestException: + # Silenciar errores y devolver una lista vacía + return [] + +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.versions = get_available_versions() # Obtener las versiones desde el archivo JSON + self.tag = self.add(npyscreen.TitleSelectOne, max_height=10, name="Selecciona la versión", + values=self.versions, scroll_exit=True) + + 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: + npyscreen.notify_confirm("Debes seleccionar al menos un componente.", title="Error") + return + if not self.tag.value: + npyscreen.notify_confirm("Debes seleccionar una versión.", title="Error") + return + selected_tag = self.versions[self.tag.value[0]] # Usar la versión seleccionada + 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 + +class ComponentForm(npyscreen.ActionForm): + component_name = None + + def create(self): + self.fields = {} + + def beforeEditing(self): + npyscreen.blank_terminal() + self.fields.clear() + self._recreate_form() + + def _recreate_form(self): + """Limpia y recrea los widgets del formulario.""" + self._clear_widgets() + 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 = {} + + for key, field_data in self.fields.items(): + component_config[key] = field_data["widget"].value + + self.parentApp.configurations[self.component_name] = component_config + + 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) + + def on_cancel(self): + if npyscreen.notify_yes_no("¿Estás seguro de que deseas salir?", title="Confirmación"): + self.parentApp.setNextForm(None) + +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")} + +class OgGuiForm(ComponentForm): + component_name = "oggui" + + def configure_fields(self): + self.fields["ogcoreUrl"] = {"widget": self.add(npyscreen.TitleText, name="URL API OgCore:", value="https://127.0.0.1:8443")} + self.fields["ogmercureUrl"] = {"widget": self.add(npyscreen.TitleText, name="Mercure URL:", value="https://127.0.0.1:3000/.well-known/mercure")} + +class OgDhcpForm(ComponentForm): + component_name = "ogdhcp" + interfaces = get_network_interfaces() + def configure_fields(self): + self.fields["interfaces"] = {"widget": self.add(npyscreen.TitleText, name="Interfaces ({}):".format(self.interfaces), value=self.interfaces)} + self.fields["ip"] = {"widget": self.add(npyscreen.TitleText, name="IP del servidor DHCP:", value="192.168.2.2")} + self.fields["ogbootIP"] = {"widget": self.add(npyscreen.TitleText, name="IP del servidor Boot:", value="192.168.2.2")} + +class OgBootForm(ComponentForm): + component_name = "ogboot" + + def configure_fields(self): + self.fields["ip"] = {"widget": self.add(npyscreen.TitleText, name="IP del servidor Boot:", value="192.168.2.2")} + 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="https://192.168.2.2:8443")} + self.fields["ogliveUrl"] = {"widget": self.add(npyscreen.TitleText, name="URL OgLive:", value="https://ognproject.evlt.uma.es/oglive/...")} + 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")} + +class OgRepositoryForm(ComponentForm): + component_name = "ogrepository" + + def configure_fields(self): + self.fields["ogrepoIp"] = {"widget": self.add(npyscreen.TitleText, name="IP del Repositorio:", value="192.168.2.2")} + self.fields["ogcoreIp"] = {"widget": self.add(npyscreen.TitleText, name="IP de OgCore:", value="192.168.2.2")} + 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.""" + log_file_path = os.path.join(CONFIGS_DIR, "installation.log") + installed_packages = [] # Lista de paquetes instalados correctamente + failed_packages = [] # Lista de paquetes que fallaron + try: + with open(log_file_path, "w") as log_file: + total_packages = len(components) + 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") + + # 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 + ) + + # 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() + + # 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 + break # Detener la instalación si ocurre un error + else: + log_file.write(f"Paquete {package} instalado correctamente.\n") + 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") + + # Mostrar un resumen al final + print("\n--- Resumen de la instalación ---") + print(f"Paquetes instalados correctamente: {len(installed_packages)}") + for pkg in installed_packages: + print(f" - {pkg}") + if failed_packages: + print(f"\nPaquetes que fallaron: {len(failed_packages)}") + for pkg in failed_packages: + print(f" - {pkg}") + else: + print("\nTodos los paquetes se instalaron correctamente.") + print(f"\nConsulta el archivo de registro para más detalles: {log_file_path}") + +class MyApp(npyscreen.NPSAppManaged): + def onStart(self): + self.addForm("MAIN", ComponentSelectionForm) + self.addForm("ogcore", OgCoreForm) + self.addForm("oggui", OgGuiForm) + self.addForm("ogdhcp", OgDhcpForm) + self.addForm("ogboot", OgBootForm) + self.addForm("ogrepository", OgRepositoryForm) + + def generate_debconf(self): + # Comprobar si la clave pública ya existe + key_path = "/etc/apt/trusted.gpg.d/opengnsys.gpg" + if os.path.exists(key_path): + # Silenciar este mensaje + pass + else: + # Añadir la clave pública + try: + subprocess.run( + 'curl -k -L https://ognproject.evlt.uma.es/debian-opengnsys/public.key | gpg --dearmour -o /etc/apt/trusted.gpg.d/opengnsys.gpg', + shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True + ) + except subprocess.CalledProcessError: + # Silenciar errores + return + + # Añadir el repositorio + try: + selected_tag = self.selected_tag # Obtener el tag seleccionado + repo_line = f'deb http://ognproject.evlt.uma.es/debian-opengnsys/opengnsys-devel/{selected_tag} noble main' + with open('/etc/apt/sources.list.d/opengnsys.list', 'w') as repo_file: + repo_file.write(repo_line + '\n') + except Exception: + # Silenciar errores + return + + # Actualizar los repositorios + try: + subprocess.run('apt-get update', shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True) + except subprocess.CalledProcessError: + # Silenciar errores + return + + # Generar configuraciones para debconf + output_file = os.path.join(CONFIGS_DIR, "configurations.txt") + try: + with open(output_file, "w") as f: + f.write("\n--- Configuraciones para debconf-set-selections ---\n") + for component, config in self.configurations.items(): + for key, value in config.items(): + field_type = "password" if "Pass" in key else "string" + line = f'echo "{component} opengnsys/{component}_{key} {field_type} {value}" | debconf-set-selections\n' + f.write(line) + + # Ejecutar la línea directamente y redirigir la salida a /dev/null + subprocess.run( + line, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) + except Exception: + # Silenciar errores + pass + + # 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) + +if __name__ == "__main__": + MyApp().run() diff --git a/non_graf_installer/python-installer/requirements.txt b/non_graf_installer/python-installer/requirements.txt index 54171c6..7d7c972 100644 --- a/non_graf_installer/python-installer/requirements.txt +++ b/non_graf_installer/python-installer/requirements.txt @@ -1,2 +1,4 @@ GitPython npyscreen +requests +tqdm