#!/usr/bin/env python3
##################################################################################
#####                       ogBoot installer script                           ####
#####  Developed by: Luis Gerardo Romero García       <lromero@qindel.com>    ####
#####                Antonio Emmanuel Guerrero Silva  <aguerrero@qindel.com>  ####
#####          Last: 2024-07-08                                               ####
##################################################################################

import platform, os, sys, subprocess, datetime, shutil, pwd, glob, logging, distro, re, json
import tempfile

ipxe_repo_url = "https://github.com/ipxe/ipxe.git"

SAMBACFGDIR = "/etc/samba"
TFTPCFGDIR = "/var/lib/tftpboot"

PROGRAM = os.path.splitext(os.path.basename(sys.argv[0]))[0]
PROGRAM_DIR = os.path.dirname(os.path.realpath(sys.argv[0]))
REPO_DIR = os.path.realpath (os.path.join (os.path.dirname (sys.argv[0]), '..'))
PROGRAM_NAME = os.path.basename(sys.argv[0])

config_file = os.path.join(PROGRAM_DIR, 'config.json')
with open(config_file, 'r') as f:
    config = json.load(f)

#OGCORE_IP = config["ogCore_ServerIP"]

ogcore_ip_port = config['ogCore_ServerIP']
if ':' in ogcore_ip_port:
    OGCORE_IP, OGCORE_PORT = ogcore_ip_port.split(':')
else:
    OGCORE_IP = ogcore_ip_port
    OGCORE_PORT = "8443"

#Para el caso de que pasen una ip con puerto separamos la ip y el puerto
#Pasamos el puerto para conexiones por http
#No pasamos el puerto para conexiones tftp y parámetros del kernel
#Si solo pasan la IP usaremos el puerto por defecto 8082
ogboot_ip_port = config['ogBoot_ServerIP']
if ':' in ogboot_ip_port:
    OGBOOT_IP, OGBOOT_PORT = ogboot_ip_port.split(':')
else:
    OGBOOT_IP = ogboot_ip_port
    OGBOOT_PORT = "8082"
oglive_iso_url = config["ogLive_Default"]
INSTALL_OGBOOT_TARGET = config["ogBoot_Dir"]
OPENGNSYS_CLIENT_USER = config["ogBootSambaUser"]
OPENGNSYS_CLIENT_PASSWD = config["ogBootSambaPass"]

DEFAULTDEV = ""
DEVICE = []
SERVERIP = []
NETIP = []
NETMASK = []
NETBROAD = []
ROUTERIP = []
BRANCH = sys.argv[1] if len(sys.argv) > 1 else "main"

UBUNTU_OS_VERSION = "24"
PYTHON_VERSION = 3

os.environ['DEBIAN_FRONTEND'] = 'noninteractive'

log_file = f'/var/log/{PROGRAM}.log'
os.makedirs(os.path.dirname(log_file), exist_ok=True)
subprocess.run(['touch', log_file])
subprocess.run(['chmod', '775', log_file])

#Configure the log
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s %(levelname)s\t%(message)s',
                    filename=f'/var/log/{PROGRAM}.log',
                    filemode='a')
logger = logging.getLogger()
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(levelname)s\t%(message)s')
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

###############################################################################
###::::::::::::::::::::::::::::::: UTILS :::::::::::::::::::::::::::::::::::###
###############################################################################
def check_python_version():
    try:
        python_version = platform.python_version()
        python_version_tuple = tuple(map(int, python_version.split('.')))
        if python_version_tuple >= (PYTHON_VERSION, 0):
            logger.info(f"Python version installed: {python_version}")
        else:
            logger.error(f"Python version is lower than required. Installed version: {python_version}" + ".")
            exit()
    except Exception as e:
        logger.error(f"Problem verifying Python version: {e}")
        exit()

def check_distribution():
    if os.path.exists("/etc/os-release"):
            with open("/etc/os-release", "r") as file:
                    for line in file:
                            if line.startswith("VERSION"):
                                    VERSION = line.split("=")[1].strip().strip('"')
                                    break
    if VERSION.startswith(UBUNTU_OS_VERSION + "."):
            return
    logger.info(f"The ogBoot installation has been tested with full functionality on Ubuntu. {UBUNTU_OS_VERSION}")
    go_on = input("Would you like to continue? [y/N]: ")
    if go_on.upper() != "Y":
            logger.error("Leaving the installation.")
            exit()

###############################################################################
###:::::::::::::::::::::::::::::: INSTALL ::::::::::::::::::::::::::::::::::###
###############################################################################

def get_missing_packages():
    PACKAGES_TO_INSTALL = []

    OSDISTRIB = distro.name()
    OSVERSION = distro.version()
    logger.info(f"OSDISTRIB: {OSDISTRIB}")
    logger.info(f"OSVERSION: {OSVERSION}")
    match OSDISTRIB.lower():
        case "ubuntu":
            match OSVERSION:
                case "20.04":
                    PACKAGES_TO_INSTALL = ["vim", "curl", "htop"]
                case "18.04":
                    PACKAGES_TO_INSTALL = ["nano", "wget", "tree"]
                case "22.04":
                    PACKAGES_TO_INSTALL = ["nfs-common", "xorriso", "genisoimage", "syslinux", "liblzma-dev", "nginx", "arp-scan", "automake", "build-essential", "btrfs-progs", "composer", "curl", "ctorrent", "debootstrap", "g++-multilib", "gawk", "gettext", "graphviz", "grub-efi-amd64-signed", "jq", "libdbi-dev", "libdbi1t64", "libev-dev", "libjansson-dev", "liblz4-tool", "libssl-dev", "moreutils", "netpipes", "php", "php-bcmath", "php-cli", "php-curl", "php-fpm", "php-gd", "php-json", "php-ldap", "php-mbstring", "php-mysql", "php8.1-common", "php-pear", "php-xml", "php-zip", "procps", "coreutils", "rsync", "samba", "samba-common-bin", "schroot", "shim-signed", "squashfs-tools", "subversion", "tftpd-hpa", "udpcast", "unzip", "wakeonlan", "wget", "xinetd", "jq", "moreutils", "net-tools", "isolinux", "syslinux"]
                case "24.04":
                    PACKAGES_TO_INSTALL = ["nfs-common", "xorriso", "genisoimage", "syslinux", "liblzma-dev", "nginx", "arp-scan", "automake", "build-essential", "btrfs-progs", "composer", "curl", "ctorrent", "debootstrap", "g++-multilib", "gawk", "gettext", "graphviz", "grub-efi-amd64-signed", "jq", "libdbi-dev", "libdbi1t64", "libev-dev", "libjansson-dev", "liblz4-tool", "libssl-dev", "moreutils", "netpipes", "php8.3", "php8.3-bcmath", "php8.3-cli", "php8.3-curl", "php8.3-fpm", "php8.3-gd", "php8.3-ldap", "php8.3-mbstring", "php8.3-mysql", "php8.3-common", "php-pear", "php8.3-xml", "php8.3-zip", "procps", "coreutils", "rsync", "samba", "samba-common-bin", "schroot", "shim-signed", "squashfs-tools", "subversion", "tftpd-hpa", "udpcast", "unzip", "wakeonlan", "wget", "xinetd", "jq", "moreutils", "isolinux", "syslinux", "file"]
                case _:
                    PACKAGES_TO_INSTALL = ["bash", "rsync"]
        #case "suse":
        #    match OSVERSION:
        #        case "15":
        #            PACKAGES_TO_INSTALL = ["zypper", "mc", "gcc"]
        #        case "12":
        #            PACKAGES_TO_INSTALL = ["perl", "tar", "man"]
        #        case _:
        #            PACKAGES_TO_INSTALL = ["openssl", "ncurses", "zip"]
        #case "redhat":
        #    match OSVERSION:
        #        case "8":
        #            PACKAGES_TO_INSTALL = ["yum", "tar", "perl"]
        #        case "7":
        #            PACKAGES_TO_INSTALL = ["bash", "rpm", "tcpdump"]
        #        case _:
        #            PACKAGES_TO_INSTALL = ["grep", "sed", "awk"]
        case _:
            logger.error("Distribution not supported by ogBoot.")
            exit(1)

    installed = []
    for l in subprocess.run (['dpkg', '-s'] + PACKAGES_TO_INSTALL, capture_output=True, text=True).stdout.splitlines():
        if l.startswith ('Package:'):
            installed.append (l.split (':')[1].strip())
    faltantes = list (set(PACKAGES_TO_INSTALL) - set(installed))
    logger.info(f"Packages to install: {faltantes}")
    return faltantes

def install_packages(log_packages_file="/tmp/installed_packages.log"):
    missing = get_missing_packages()
    if not missing:
        logger.info("All packages are already installed.")
        return

    logger.info("Upgrading the system...")

    subprocess.run(["apt-get", "update"], check=True)
    subprocess.run(
        ["apt-get", "install", "--allow-change-held-packages", "-y", "--no-install-recommends"] + missing + ['apache2-'],
        check=True
    )

    with open(log_packages_file, "a") as log:
        for package in missing:
            logger.info(f"{package} installed correctly.")
            log.write(package + "\n")

    logger.info("All missing packages have been installed.")

    # Check PHP version and install corresponding php-fpm package
    php_version = subprocess.check_output(["php", "-v"]).decode("utf-8")
    if "PHP 8.1" in php_version:
        subprocess.run(["apt-get", "install", "-y", "php8.1-fpm"], check=True)
    elif "PHP 8.3" in php_version:
        subprocess.run(["apt-get", "install", "-y", "php8.3-fpm"], check=True)
    else:
        logger.warning("PHP version not supported.")

def add_sudoers_permissions():
    sudoers_entry = """
opengnsys ALL=(ALL) NOPASSWD: /opt/bin/oglivecli
opengnsys ALL=(root) NOPASSWD: /usr/bin/chmod, /usr/bin/chown, /usr/bin/md5sum, /usr/bin/smbpasswd, /usr/bin/cat, /usr/bin/tee, /usr/bin/sed, /usr/bin/gzip, /usr/bin/lz4, /usr/bin/cpio, /usr/bin/find, /bin/tee, /usr/bin/dd, /usr/bin/mkfs.ext4, /usr/bin/rsync
opengnsys ALL=(root) NOPASSWD: __OGBOOT_TARGET__/lib/*.iso /mnt
    """

    sudoers_file = '/etc/sudoers.d/ogboot'

    try:
        with open(sudoers_file, 'w') as file:
            file.write(sudoers_entry.replace ('__OGBOOT_TARGET__', INSTALL_OGBOOT_TARGET))
        print("Sudoers permissions for 'opengnsys' added successfully.")
    except IOError as e:
        print(f"Failed to write to {sudoers_file}: {e}")


def og_core_create_user(u):
    try:
        user_info = pwd.getpwnam(u)
        current_home = user_info.pw_dir
        logger.info(f"User {u} already exists with home {current_home}")

        if u == "opengnsys" and current_home != "/opt/opengnsys":
            logger.info(f"Updating home directory for user {u} to /opt/opengnsys")
            
            subprocess.run(["usermod", "-d", "/opt/opengnsys", "-m", u], check=True)
            logger.info(f"User {u} home changed successfully to /opt/opengnsys")
        else:
            logger.info(f"User {u} already has the correct home directory.")
    
    except KeyError:
        # El usuario no existe, crearlo con el home correcto, si es opengnsys le ponemos su home_dir como opt/opengnsys
        # Si no es opengnsys le creamos el home en /home/<usuario>
        home_dir = "/opt/opengnsys" if u == "opengnsys" else f"/home/{u}"
        logger.info(f"Creating user {u} with home {home_dir}")
        subprocess.run(["useradd", "--create-home", "-d", home_dir, "--shell", "/bin/bash", u], check=True)
        logger.info(f"User {u} created successfully with home {home_dir}")

def og_boot_create_dirs():
    if os.path.exists(INSTALL_OGBOOT_TARGET):
        if not os.path.isdir(INSTALL_OGBOOT_TARGET):
            raise NotADirectoryError(f"{INSTALL_OGBOOT_TARGET} exists and is not a directory.")
        else:
            logger.warning(f"{INSTALL_OGBOOT_TARGET} directory already exists.")
    else:
        try:
            # Crear los directorios necesarios
            os.makedirs("/opt/opengnsys", mode=0o775, exist_ok=True)
            subprocess.run(["chmod", "775", "/opt/opengnsys"])
            os.makedirs(INSTALL_OGBOOT_TARGET, mode=0o775, exist_ok=True)
            api_dir = os.path.join(INSTALL_OGBOOT_TARGET, "api")
            os.makedirs(api_dir, mode=0o775, exist_ok=True)

            # Cambiar el propietario de los directorios
            subprocess.run(["chown", "-R", "opengnsys:opengnsys", INSTALL_OGBOOT_TARGET])


            logger.info(f"{INSTALL_OGBOOT_TARGET} directory created successfully.")
        except OSError:
            logger.error("Error while creating directory paths!")
            exit(1)

def og_boot_symfony_install():
    logger.info("Creating Symfony application skeleton...")

    try:
        api_dir = os.path.join(INSTALL_OGBOOT_TARGET, "api")
        os.makedirs(api_dir, exist_ok=True)  # Asegurar que el directorio api existe
        # Cambio de .env y composer.json bajo /api
        api_source = os.path.join(REPO_DIR, "api")
        env_src = os.path.join(api_source, ".env")
        composer_src = os.path.join(api_source, "composer.json")
        env_dest = os.path.join(api_dir, ".env")
        composer_dest = os.path.join(api_dir, "composer.json")

        shutil.copy(env_src, env_dest)
        shutil.copy(composer_src, composer_dest)
        logger.info(f"Copied environment source {env_src} to {env_dest}")
        logger.info(f"Copied composer source {composer_src} to {composer_dest}")

        # Cambiar permisos y propietario de los archivos copiados
        os.chmod(env_dest, 0o644)
        os.chmod(composer_dest, 0o644)
        shutil.chown(env_dest, user='opengnsys', group='opengnsys')
        shutil.chown(composer_dest, user='opengnsys', group='opengnsys')
        logger.info(f"Set permissions and owner for {env_dest} and {composer_dest}")

        # Añadir la línea OGCORE_API_URL utilizando OGCORE_IP y OGCORE_PORT
        ogcore_api_url = f'OGCORE_API_URL="https://{OGCORE_IP}:{OGCORE_PORT}"'
        with open(env_dest, 'a') as env_file:
            env_file.write(f"\n{ogcore_api_url}\n")
        logger.info(f"Added OGCORE_API_URL to {env_dest} with IP: {OGCORE_IP}")
  
        ogboot_ip = f'OGBOOT_IP="{OGBOOT_IP}"'
        with open(env_dest, 'a') as env_file:
            env_file.write(f"\n{ogboot_ip}\n")
        logger.info(f"Added OGBOOT_IP to {env_dest} with IP: {OGBOOT_IP}")
        ogboot_port = f'OGBOOT_PORT="{OGBOOT_PORT}"'
        with open(env_dest, 'a') as env_file:
            env_file.write(f"\n{ogboot_port}\n")
        logger.info(f"Added OGBOOT_PORT to {env_dest} with IP: {OGBOOT_PORT}")

    except Exception as e:
        logger.error(f"An error occurred while copying files or modifying .env: {e}")
        raise



def og_boot_copy_files():
    api_dir = os.path.join(INSTALL_OGBOOT_TARGET, "api")
    api_source = os.path.join(REPO_DIR, "api")

    # api/bin que contendría los scripts composer y console de symfony
    bin_api_source = os.path.join(api_source, "bin")
    bin_api_dest = os.path.join(api_dir, "bin")
    # api/bin que contendría los scripts oglivecli y setsmbpass
    bin_source = os.path.join(REPO_DIR, "bin")
    bin_dest = os.path.join(INSTALL_OGBOOT_TARGET, "bin")

    src_source = os.path.join(api_source, "src")
    src_dest = os.path.join(api_dir, "src")
    config_source = os.path.join(api_source, "config")
    config_dest = os.path.join(api_dir, "config")
    # lib va fuera de /api
    lib_source = os.path.join(REPO_DIR, "lib")
    lib_dest = os.path.join(INSTALL_OGBOOT_TARGET, "lib")

    if os.path.exists(bin_dest):
        shutil.rmtree(bin_dest)
    shutil.copytree(bin_source, bin_dest)

    if os.path.exists(bin_api_dest):
        shutil.rmtree(bin_api_dest)
    shutil.copytree(bin_api_source, bin_api_dest)

    if os.path.exists(src_dest):
        shutil.rmtree(src_dest)
    shutil.copytree(src_source, src_dest)

    if os.path.exists(config_dest):
        shutil.rmtree(config_dest)
    shutil.copytree(config_source, config_dest)

    if os.path.exists(lib_dest):
        shutil.rmtree(lib_dest)
    shutil.copytree(lib_source, lib_dest)

    os.makedirs(os.path.join(INSTALL_OGBOOT_TARGET, "etc"), mode=0o775, exist_ok=True)
    os.makedirs(os.path.join(INSTALL_OGBOOT_TARGET, "client"), mode=0o775, exist_ok=True)
    public_dir = os.path.join(api_dir, "public")
    os.makedirs(public_dir, mode=0o775, exist_ok=True)

    subprocess.run(["chmod", "-R", "775", INSTALL_OGBOOT_TARGET])
    subprocess.run(["chown", "-R", "opengnsys:opengnsys", INSTALL_OGBOOT_TARGET])

def og_boot_composer_install():
    api_dir = os.path.join(INSTALL_OGBOOT_TARGET, "api")
    result = subprocess.run(
        ["sudo", "-u", "opengnsys", "composer", "install", "--no-interaction", "--working-dir", api_dir]
    )

    # Ejecutar Composer como el usuario 'opengnsys' para actualizar el paquete doctrine/dbal
    result = subprocess.run(["sudo", "-u", "opengnsys", INSTALL_OGBOOT_TARGET+"/api/bin/composer.phar", "update", "doctrine/dbal", "--working-dir", api_dir])
    if result.returncode != 0:
        logger.error("Error updating doctrine/dbal package using Composer")
        return

    subprocess.call(["chown", "-R", "opengnsys:opengnsys", f"{api_dir}/public"])

    logger.info("Application skeleton created.")

###############################################################################
###:::::::::::::::::::::::::::: CONFIGURE ::::::::::::::::::::::::::::::::::###
###############################################################################

# Obtener la UID y GID del usuario ogboot
def get_ogboot_uid_gid():
    try:
        user_info = pwd.getpwnam('opengnsys')
        uid = user_info.pw_uid
        gid = user_info.pw_gid
        return uid, gid
    except KeyError:
        raise Exception("El usuario 'opengnsys' no existe.")

# Añadir líneas al fstab
def add_fstab_entries(uid, gid):
    try:
        fstab_entries = [
            f'{INSTALL_OGBOOT_TARGET}/lib/oglive.iso {INSTALL_OGBOOT_TARGET}/mnt iso9660 loop,ro,users,uid={uid},gid={gid},noauto 0 0\n',
            f'/var/lib/tftpboot/ogLive/ogclient.sqfs /tmp/ogclient_mount squashfs loop,ro,user,noauto 0 0\n'
        ]

        with open('/etc/fstab', 'r') as fstab:
            existing_entries = fstab.readlines()

        # Check if the entries already exist in /etc/fstab
        for entry in fstab_entries:
            if entry not in existing_entries:
                with open('/etc/fstab', 'a') as fstab:
                    fstab.writelines(entry)
            logger.info("Entradas añadidas a /etc/fstab correctamente.")
        else:
            logger.info("Las entradas ya existen en /etc/fstab. No se añadieron nuevamente.")

    except IOError:
        raise Exception("Error al escribir en /etc/fstab.")

# Añadir el usuario opengnsys al grupo disk
def add_user_to_disk_group():
    try:
        subprocess.run(['usermod', '-aG', 'disk', 'opengnsys'], check=True)
        logger.info("Usuario 'opengnsys' añadido al grupo 'disk' correctamente.")
    except subprocess.CalledProcessError:
        raise Exception("Error al añadir el usuario 'opengnsys' al grupo 'disk'.")

def tftpConfigure():
    logger.info("Configuring tftpd-hpa...")
    tftpd_config = """# /etc/default/tftpd-hpa

TFTP_USERNAME="tftp"
TFTP_DIRECTORY="/var/lib/tftpboot"
TFTP_ADDRESS="0.0.0.0:69"
TFTP_OPTIONS="--secure -v"
"""
    with open("/tmp/tftpd-hpa", "w") as config_file:
        config_file.write(tftpd_config)

    shutil.move("/tmp/tftpd-hpa", "/etc/default/tftpd-hpa")

    logger.info("\t2-Creating and setting permissions for the TFTP directory...")
    os.makedirs(TFTPCFGDIR, exist_ok=True)

    logger.info("\t3-Setting permissions for /var/lib/tftpboot directory...")
    subprocess.run(f"chown -R tftp:opengnsys {TFTPCFGDIR}", shell=True, text=True, capture_output=True)
    subprocess.run(f"chmod -R 775 {TFTPCFGDIR}", shell=True, text=True, capture_output=True)

    subprocess.run("systemctl restart tftpd-hpa", shell=True, text=True, capture_output=True)

    symlink_target = f"{INSTALL_OGBOOT_TARGET}/tftpboot"
    logger.info(f"Creating symbolic link from {TFTPCFGDIR} to {symlink_target}")
    if not os.path.exists(symlink_target):
        os.symlink(TFTPCFGDIR, symlink_target)
        #os.lchown(symlink_target, pwd.getpwnam("tftp").pw_uid, pwd.getpwnam("opengnsys").pw_gid)
    else:
        logger.warning(f"The symbolic link already exists: {symlink_target}")

    logger.info("Downloading oglive...")
    try:
        result = subprocess.run(
            [INSTALL_OGBOOT_TARGET+"/bin/oglivecli", "download", oglive_iso_url],
            check=True,
            capture_output=True,
            text=True
        )
        # Comprobar si oglivecli ha fallado
        if result.returncode != 0:
            try:
                # Parsear la salida como JSON
                error_output = json.loads(result.stdout)
                if error_output.get("status") == "error":
                    logger.error(f"oglivecli error: {error_output.get('error')}")
            except json.JSONDecodeError:
                logger.error("Failed to parse oglivecli error output.")
            logger.error("Continuing with the installation despite oglivecli failure.")
        else:
            logger.info("Successful download")
            symlink_target_ogLive = f"{INSTALL_OGBOOT_TARGET}/tftpboot/ogLive"
            symlink_target_ogclient = f"{INSTALL_OGBOOT_TARGET}/tftpboot/ogclient"
            if os.path.exists(symlink_target_ogLive):
                subprocess.run(["chown", "-R", f"tftp:opengnsys", f"{INSTALL_OGBOOT_TARGET}/tftpboot"], check=True)
                os.lchown(symlink_target_ogLive, pwd.getpwnam("tftp").pw_uid, pwd.getpwnam("opengnsys").pw_gid)
                os.lchown(symlink_target_ogclient, pwd.getpwnam("tftp").pw_uid, pwd.getpwnam("opengnsys").pw_gid)
                logger.info(f"Changing properties for {symlink_target_ogLive} and {symlink_target_ogclient}")
            else:
                logger.error(f"{symlink_target_ogLive} link does not exist.")
    except subprocess.CalledProcessError as e:
        logger.error(f"Subprocess failed: {e}")
        logger.error("Continuing with the installation...")

def get_first_network_interface_with_traffic():
    with open('/proc/net/dev') as f:
        for line in f:
            if ':' in line:
                parts = line.split(':')
                if len(parts) > 1:
                    interface = parts[0].strip()
                    if interface != "lo":
                        traffic_data = parts[1].strip().split()
                        received_bytes = int(traffic_data[0])
                        transmitted_bytes = int(traffic_data[8])
                        if received_bytes > 0 or transmitted_bytes > 0:
                            return interface



def install_ipxe():
    clone_dir = "/tmp/ipxe_repo"
    if os.path.exists(f"{INSTALL_OGBOOT_TARGET}/tftpboot/undionly.kpxe"):
        logger.info('iPXE already present--not compiling again')
        return True

    if not os.path.exists(clone_dir):
        os.makedirs(clone_dir)
        logger.info(f"Cloning the repository {ipxe_repo_url}")
        if subprocess.call(["git", "-c", "http.sslVerify=false", "clone", ipxe_repo_url, clone_dir]) == 0:
            logger.info("Repository cloned successfully.")
        else:
            logger.error(f"ERROR: Could not clone the repository {ipxe_repo_url}.")
            return False
    else:
        logger.info(f"Repository already exists at {clone_dir}")

    cwd = os.getcwd()
    os.chdir(f"{clone_dir}/src")

    logger.info("Generating make of undionly.kpxe:")
    if subprocess.run(["make", "-s", "bin/undionly.kpxe", f"EMBED={INSTALL_OGBOOT_TARGET}/tftpboot/ipxe_scripts/dhcp_boot.ipxe"], capture_output=True).returncode == 0:
        logger.info("Boot file mounted correctly.")
    else:
        logger.error("Failed to mount boot file.")
        return False
    logger.info("Copying undionly.kpxe with user opengnsys:")
    subprocess.call(["cp", "bin/undionly.kpxe", f"{INSTALL_OGBOOT_TARGET}/tftpboot"])

    logger.info("Generating make of ipxe.efi:")
    if subprocess.run(["make", "-s", "bin-x86_64-efi/ipxe.efi", f"EMBED={INSTALL_OGBOOT_TARGET}/tftpboot/ipxe_scripts/dhcp_boot.ipxe"], capture_output=True).returncode == 0:
        logger.info("Properly constructed EFI file.")
    else:
        logger.error("Could not build EFI file.")
        return False
    subprocess.call(["cp", "bin-x86_64-efi/ipxe.efi", f"{INSTALL_OGBOOT_TARGET}/tftpboot"])
    subprocess.call(["cp", f"{REPO_DIR}/tftpboot/grub.exe", f"{INSTALL_OGBOOT_TARGET}/tftpboot/"])
    os.makedirs(f"{INSTALL_OGBOOT_TARGET}/tftpboot/ipxe_scripts/templates", exist_ok=True)
    subprocess.call(["cp", "-r", f"{REPO_DIR}/tftpboot/ipxe_scripts/templates/.", f"{INSTALL_OGBOOT_TARGET}/tftpboot/ipxe_scripts/templates"])
    subprocess.run(["chmod", "-R", "775", f"{INSTALL_OGBOOT_TARGET}/tftpboot/"])
    subprocess.call(["chown", "-R", "opengnsys:opengnsys", f"{INSTALL_OGBOOT_TARGET}/tftpboot/"])

    os.chdir(cwd)
    return True

def get_ip_address(interface):
    try:
        result = subprocess.check_output(["ip", "addr", "show", interface]).decode()
        for line in result.split('\n'):
            if "inet " in line:
                ip_address = line.strip().split()[1].split('/')[0]
                return ip_address
    except subprocess.CalledProcessError as e:
        logger.error(f"Error get address IP: {e}")
        return None

def generate_ipxe_script():
    ip_address_server = OGBOOT_IP
    ip_address = re.match(r"^(.*?):", ip_address_server)
    ip_address = ip_address.group(1) if ip_address else ip_address_server
    template = os.path.join(REPO_DIR, "etc/dhcp_boot.ipxe.tmpl")
    ipxe_output = f"{INSTALL_OGBOOT_TARGET}/tftpboot/ipxe_scripts/dhcp_boot.ipxe"
    os.makedirs(os.path.dirname(ipxe_output), mode=0o775, exist_ok=True)
    #shutil.copy(template, ipxe_output)
    with open(template, "r") as ipxe_file:
        ipxe_content = ipxe_file.read()
        ipxe_content = ipxe_content.replace("__SERVERIP__", ip_address)
    with open(ipxe_output, "w") as ipxe_file:
        ipxe_file.write(ipxe_content)

    template_default = os.path.join(REPO_DIR, "etc/default.ipxe.tmpl")
    default_output = os.path.join(INSTALL_OGBOOT_TARGET, "tftpboot/ipxe_scripts/default.ipxe")
    with open(template_default, "r") as default_tmpl_file:
        default_template_content = default_tmpl_file.read()
        default_ipxe_content = default_template_content.replace("__SERVERIP__", ip_address)
        with open(default_output, "w") as default_ipxe_file:
            default_ipxe_file.write(default_ipxe_content)
    logger.info("ipxe files created correctly.")

def user_exists(user):
    try:
        result = subprocess.run(["pdbedit", "-L", "-u", user], capture_output=True, text=True)
        return user in result.stdout
    except subprocess.CalledProcessError as e:
        logger.error(f"Error checking if user exists: {e}")
        return False

def smbConfigure():
    smb_conf = f"{SAMBACFGDIR}/smb.conf"
    if os.path.exists (smb_conf):
        dateymd = datetime.datetime.now().strftime("%Y%m%d")
        shutil.copy2 (smb_conf, f"{smb_conf}-{dateymd}")

    # Copiar plantilla de recursos para OpenGnsys
    with open(os.path.join(REPO_DIR, 'etc/smb-ogboot.conf.tmpl'), 'r') as tmpl_file:
        template = tmpl_file.read()
        replaced_template = template.replace('__OGBOOTDIR__', INSTALL_OGBOOT_TARGET)
    with open(os.path.join(SAMBACFGDIR, 'smb-ogboot.conf'), 'w') as conf_file:
        conf_file.write(replaced_template)
    # Configurar y recargar Samba"
    subprocess.run(["perl", "-pi", "-e", "s/WORKGROUP/OPENGNSYS/; s/server string \\=.*/server string \\= ogBoot Samba Server/", smb_conf])
    if "smb-ogboot" not in open(smb_conf).read():
        with open(smb_conf, "a") as file:
            file.write(f"include = {SAMBACFGDIR}/smb-ogboot.conf\n")
    service = "smbd"
    logger.info(f"Enabling {service} service.")
    subprocess.run(["systemctl", "enable", f"{service}.service"])
    logger.info(f"Restarting {service} service.")
    subprocess.run(["systemctl", "restart", f"{service}.service"])
    # Comprobar si se ha configurado correctamente Samba
    if subprocess.run(["systemctl", "is-active", f"{service}.service"]).returncode == 0:
        logger.info(f"{service} service started successfully.")
    else:
        logger.error(f"Failed to start {service} service.")
        return 1
    # Ejecutar el comando smbpasswd para agregar el usuario de Samba
    try:
        if user_exists(OPENGNSYS_CLIENT_USER):
            logger.info(f"{OPENGNSYS_CLIENT_USER} user exists. Changing password...")
            extra_params = []
        else:
            logger.info(f"{OPENGNSYS_CLIENT_USER} user does not exist. Registering user...")
            extra_params = ['-a']

        subprocess.run(
            ["smbpasswd"] + extra_params + [OPENGNSYS_CLIENT_USER],
            input=f"{OPENGNSYS_CLIENT_PASSWD}\n{OPENGNSYS_CLIENT_PASSWD}\n",
            text=True,
            capture_output=True,
            check=True
        )
        logger.info("Add/Modify user: Operation completed successfully.")
    except subprocess.CalledProcessError as e:
        logger.error(f"Error adding/modifying user: {e}")
    return 0

def setup_nginx():
    try:
        # Obtener la IP del servidor
        ip_address_server = OGBOOT_IP
        port_address_server = OGBOOT_PORT
        php_version = get_php_fpm_version()
        api_dir = os.path.join(INSTALL_OGBOOT_TARGET, "api")
        # Leer y modificar la plantilla de configuración de nginx
        template_path = os.path.join(REPO_DIR, "etc/nginxServer.conf.tmpl")
        with open(template_path, 'r') as nginx_file:
            nginx_content = nginx_file.read()
        
        nginx_content = nginx_content.replace("__SERVERIP__", ip_address_server)
        nginx_content = nginx_content.replace("__PORT__", port_address_server)
        nginx_content = nginx_content.replace("__PHPVERSION__", php_version)
        nginx_content = nginx_content.replace("__ROOT__", api_dir)
        nginx_content = nginx_content.replace("__TFTPPATH__", f"{INSTALL_OGBOOT_TARGET}/tftpboot")
 
        # Ruta de destino para la configuración de nginx
        nginx_output = "/etc/nginx/sites-available/ogboot.conf"
        with open(nginx_output, 'w') as nginx_file:
            nginx_file.write(nginx_content)

        logger.info("Nginx configuration file created successfully.")

        # Crear el enlace simbólico en sites-enabled
        subprocess.run(["ln", "-sf", nginx_output, "/etc/nginx/sites-enabled/ogboot.conf"])

        logger.info("Symbolic link for nginx configuration created successfully.")

        # Modificar el archivo de configuración de nginx para ejecutarse como ogboot
        nginx_conf_path = "/etc/nginx/nginx.conf"
        with open(nginx_conf_path, 'r') as nginx_conf_file:
            nginx_conf_content = nginx_conf_file.read()

        nginx_conf_content = nginx_conf_content.replace("user www-data;", "user opengnsys;")

        with open(nginx_conf_path, 'w') as nginx_conf_file:
            nginx_conf_file.write(nginx_conf_content)

        logger.info("Nginx configuration file modified to run as opengnsys.")

        # Reiniciar el servicio de samba
        subprocess.run(["systemctl", "restart", "nginx.service"])

    except subprocess.CalledProcessError as e:
        logger.error(f"Subprocess error: {e}")
        exit(1)
    except OSError as e:
        logger.error(f"OS error: {e}")
        exit(1)

def get_php_fpm_version():
    try:
        # Obtener la versión de PHP
        php_version_output = subprocess.check_output(["php", "-v"]).decode()
        # Extraer la versión principal y secundaria (por ejemplo, "7.4")
        match = re.search(r"PHP (\d+\.\d+)", php_version_output)
        if match:
            php_version = match.group(1)
            return php_version
        else:
            raise RuntimeError("No se pudo determinar la versión de PHP.")
    except subprocess.CalledProcessError as e:
        logger.error(f"Error al obtener la versión de PHP: {e}")
        exit(1)

def modify_php_fpm_config():
    php_version = get_php_fpm_version()
    php_fpm_conf_path = f"/etc/php/{php_version}/fpm/pool.d/www.conf"
    new_fpm_conf_path = f"/etc/php/{php_version}/fpm/pool.d/ogboot.conf"
    socket_path = f"/run/php/php{php_version}-fpm-ogboot.sock"

    if os.path.exists(new_fpm_conf_path):
        logger.info(f"Archivo {new_fpm_conf_path} ya existe. No se realizarán modificaciones.")
        return

    try:
        # Copiar www.conf a ogboot.conf
        subprocess.run(["cp", php_fpm_conf_path, new_fpm_conf_path], check=True)
        logger.info(f"Archivo {php_fpm_conf_path} copiado a {new_fpm_conf_path}")

        # Leer el archivo copiado opengnsys.conf
        with open(new_fpm_conf_path, 'r') as file:
            config_lines = file.readlines()

        # Modificar las líneas necesarias
        with open(new_fpm_conf_path, 'w') as file:
            for line in config_lines:
                if line.startswith('[www]'):
                    file.write('[ogboot]\n')  # Cambiar el nombre del pool
                elif line.startswith('user ='):
                    file.write('user = opengnsys\n')
                elif line.startswith('group ='):
                    file.write('group = opengnsys\n')
                elif line.startswith('listen ='):
                    file.write(f'listen = {socket_path}\n')  # Cambiar el nombre del socket
                elif line.startswith('listen.owner ='):
                    file.write('listen.owner = opengnsys\n')
                elif line.startswith('listen.group ='):
                    file.write('listen.group = opengnsys\n')
                else:
                    file.write(line)

        logger.info(f"Archivo {new_fpm_conf_path} modificado correctamente.")

        # Reiniciar el servicio PHP-FPM
        subprocess.run(["systemctl", "restart", f"php{php_version}-fpm"], check=True)
        logger.info("Servicio PHP-FPM reiniciado correctamente.")
        subprocess.run(["systemctl", "restart", "nginx.service"])
        # Verificar que el socket se ha creado
        if os.path.exists(socket_path):
            logger.info(f"Socket {socket_path} creado correctamente.")
        else:
            logger.error(f"El socket {socket_path} no se ha creado.")
            exit(1)

    except Exception as e:
        logger.error(f"Ocurrió un error: {e}")
        exit(1)

###############################################################################
###:::::::::::::::::::::::::::::::: MAIN :::::::::::::::::::::::::::::::::::###
###############################################################################

logger.info(f":::::::::::::::::::::::: Starting ogBoot installation ::::::::::::::::::::::::")

logger.info("environment variables")
logger.info(f"OGCORE_IP:{OGCORE_IP}")
logger.info(f"INSTALL_OGBOOT_TARGET:{INSTALL_OGBOOT_TARGET}")

if os.geteuid() != 0:
    logger.error("This program must be run with root privileges..")
    exit(1)

## doc no existe nunca
if os.path.exists(os.path.join(INSTALL_OGBOOT_TARGET, "/doc/")):
    logger.warning(f"ogBoot is already installed. Run {INSTALL_OGBOOT_TARGET}/lib/ogboot_devel_update.py with root privileges to update..")
    exit(2)

try:
    logger.info("Verifying Python distribution and version.")
    check_distribution()
    check_python_version()
except Exception as e:
    logger.error(f"Error verifying Python distribution or version: {e}")
    exit(1)

try:
    logger.info("Installing necessary packages.")
    install_packages()
except Exception as e:
    logger.error(f"Error installing necessary packages: {e}")
    exit(1)

try:
    logger.info("Obtaining the default network configuration.")
    DEFAULTDEV = get_first_network_interface_with_traffic()
    logger.info(f"Network interface default:[{DEFAULTDEV}]")
except Exception as e:
    logger.error(f"Error obtaining network configuration: {e}")
    exit(1)

try:
    add_sudoers_permissions()
except Exception as e:
    logger.error(f"Error adding sudoers permissions: {e}")
    exit(1)

try:
    logger.info("Creating ogBoot project.")
    og_core_create_user("opengnsys")
    og_core_create_user(OPENGNSYS_CLIENT_USER)
except Exception as e:
    logger.error(f"Error creating ogBoot project or users: {e}")
    exit(1)

try:
    logger.info("Creating directories.")
    og_boot_create_dirs()
except Exception as e:
    logger.error(f"Error creating directories: {e}")
    exit(1)

try:
    logger.info("Copying installation files.")
    og_boot_copy_files()
except Exception as e:
    logger.error(f"Error copying installation files: {e}")
    exit(1)

try:
    logger.info("Installing Symfony.")
    og_boot_symfony_install()
except Exception as e:
    logger.error(f"Error installing Symfony: {e}")
    exit(1)

try:
    logger.info("Installing Composer.")
    og_boot_composer_install()
except Exception as e:
    logger.error(f"Error installing Composer: {e}")
    exit(1)

try:
    logger.info("Obteniendo UID y GID del usuario 'opengnsys'.")
    uid, gid = get_ogboot_uid_gid()

    logger.info("Añadiendo entradas al archivo /etc/fstab.")
    add_fstab_entries(uid, gid)

    logger.info("Añadiendo el usuario 'opengnsys' al grupo 'disk'.")
    add_user_to_disk_group()

except Exception as e:
    logger.error(f"Error durante la configuración: {e}")
    exit(1)

try:
    logger.info("Configuring tftpd-hpa service.")
    tftpConfigure()
except Exception as e:
    logger.error(f"Error configuring tftpd-hpa service: {e}")
    exit(1)

try:
    logger.info("Configuring IPXE services")
    generate_ipxe_script()
except Exception as e:
    logger.error(f"Error configuring IPXE services: {e}")
    exit(1)

try:
    logger.info("Installing iPXE")
    if not install_ipxe():
        logger.error(f"Error installing iPXE")
        exit(1)
except Exception as e:
    logger.error(f"Error installing iPXE: {e}")
    exit(1)

try:
    logger.info("Setup nginx")
    setup_nginx()
except Exception as e:
    logger.error(f"Error setting up nginx: {e}")
    exit(1)

try:
    logger.info("Configure php fpm")
    modify_php_fpm_config()
except Exception as e:
    logger.error(f"Error configuring php fpm: {e}")
    exit(1)

try:
    logger.info("Configuring Samba")
    smbConfigure()
except Exception as e:
    logger.error(f"Error configuring Samba: {e}")
    exit(1)

logger.info(f"ogBoot installation finished.")

logging.shutdown()
console_handler.close()

