#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2024-2025 Qindel Formación y Servicios S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
#    * Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#    * Redistributions in binary form must reproduce the above copyright notice,
#      this list of conditions and the following disclaimer in the documentation
#      and/or other materials provided with the distribution.
#    * Neither the name of Virtual Cable S.L. nor the names of its contributors
#      may be used to endorse or promote products derived from this software
#      without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
@author: Ramón M. Gómez, ramongomez at us dot es
@author: Natalia Serrano, nserrano at qindel dot com
"""

import base64
import os
import signal
import string
import random
import subprocess
from pathlib import Path
from urllib.parse import unquote

from opengnsys import VERSION
from opengnsys.log import logger
from opengnsys.workers import ogLiveWorker

# Check authorization header decorator
def check_secret (fnc):
    """
    Decorator to check for received secret key and raise exception if it isn't valid.
    """
    def wrapper (*args, **kwargs):
        try:
            this, path, get_params, post_params, server = args

            if not server:      ## this happens on startup, eg. onActivation->autoexecCliente->ejecutaArchivo->popup->check_secret
                return fnc (*args, **kwargs)

            if this.random == server.headers['Authorization']:
                return fnc (*args, **kwargs)
            else:
                raise Exception ('Unauthorized operation')
        except Exception as e:
            logger.error (str (e))
            raise Exception (e)

    return wrapper

# Check if operation is permitted
def execution_level(level):
    def check_permitted(fnc):
        def wrapper(*args, **kwargs):
            levels = ['status', 'halt', 'full']
            this = args[0]
            try:
                if levels.index(level) <= levels.index(this.exec_level):
                    return fnc(*args, **kwargs)
                else:
                    raise Exception('Unauthorized operation')
            except Exception as e:
                logger.debug (str(e))
                raise Exception(e)

        return wrapper

    return check_permitted

class ogAdmClientWorker (ogLiveWorker):
    name          = 'ogAdmClient'  # Module name
    REST          = None  # REST object

    def InventariandoSoftware (self, dsk, par, nfn):
        sft_src = f'/tmp/CSft-{self.IPlocal}-{par}'
        try:
            self.interfaceAdmin (nfn, [dsk, par, sft_src])
            herror = 0
        except:
            herror = 1

        if herror:
            logger.warning ('Error al ejecutar el comando')
            b64 = ''
            self.muestraMensaje (20)
        else:
            if not os.path.exists (sft_src):
                raise Exception (f'interfaceAdmin({nfn}) returned success but did not create file ({sft_src})')
            sft_src_contents = Path (sft_src).read_bytes()

            b64 = base64.b64encode (sft_src_contents).decode ('utf-8')
            self.muestraMensaje (19)

        cmd = {
            'nfn': 'RESPUESTA_InventarioSoftware',
            'dsk': dsk,         ## not in the original C code, around ogAdmClient.c:1944
            'par': par,
            'contents': b64,
        }
        return self.respuestaEjecucionComando (cmd, herror, 0)

    def ejecutaArchivo (self,fn):
        logger.debug ('fn ({})'.format (fn))

        ## in the "file" there's not just some bash, but a sequence of parameters such as "nfn=Function\rparam1=foo\rparam2=bar"
        buffer = subprocess.run (['cat', fn], capture_output=True).stdout.strip().decode ('utf-8')
        logger.debug ('buffer ({})'.format (buffer.replace ('\r', '\\r')))    ## change \r so as not to mess with the log
        if buffer:
            for l in buffer.split ('@'):
                if not len (l): continue
                logger.debug ('line ({})'.format (l.replace ('\r', '\\r')))    ## change \r so as not to mess with the log
                ## at this point, an option would be fire up a curl to localhost, but we can also parse the params and locally call the desired function:
                post_params = {}
                for param in l.split ("\r"):
                    k, v = param.split ('=')
                    post_params[k] = v
                logger.debug ('post_params "{}"'.format (post_params))

                func_name = post_params.pop ('nfn', None)
                if func_name is None:
                    logger.error ('Ha ocurrido algún problema al procesar la trama recibida')
                    break
                func = getattr (self, 'process_' + func_name)
                ## func is already a ref to self.func, so we don't have to call self.func(...) or func(self, ...)

                logger.debug ('calling function "{}" with post_params "{}"'.format (func_name, post_params))
                output = func ([], {}, post_params, None)
                logger.debug ('output "{}"'.format (output))
                if not output:
                    logger.error ('Ha ocurrido algún problema al procesar la trama recibida')
                    break

    def inclusionCliente (self):
        cfg = self.LeeConfiguracion()
        if not cfg:
            logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
            logger.warning ('Ha ocurrido algún problema en el proceso de inclusión del cliente')
            logger.error ('LeeConfiguracion() failed')
            return False
        res = self.enviaMensajeServidor ('InclusionCliente', { 'cfg': self.cfg2obj (cfg), 'secret': self.random, 'agent_version': VERSION })
        logger.debug ('res ({})'.format (res))

        ## RESPUESTA_InclusionCliente
        if (type (res) is not dict or 0 == res['res']) :
            logger.error ('Ha ocurrido algún problema en el proceso de inclusión del cliente')
            return False

        if (not res['ido'] or not res['npc']):
            logger.error ('Se han recibido parámetros con valores no válidos')
            return False

        self.idordenador     = res['ido']   ## Identificador del ordenador
        self.nombreordenador = res['npc']   ## Nombre del ordenador
        self.cache           = res['che']   ## Tamaño de la caché reservada al cliente
        self.idproautoexec   = res['exe']   ## Procedimento de inicio (Autoexec)
        self.idcentro        = res['idc']   ## Identificador de la Unidad Organizativa
        self.idaula          = res['ida']   ## Identificador del aula

        return True

    def cuestionCache (self):
        return True         ## ogAdmClient.c:425

    def autoexecCliente (self):
        res = self.enviaMensajeServidor ('AutoexecCliente', { 'exe': self.idproautoexec })
        logger.debug ('res ({})'.format (res))

        if (type (res) is not dict):
            logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración')
            logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración')
            return False

        ## RESPUESTA_AutoexecCliente
        if (type (res) is not dict or 0 == res['res']) :
            logger.error ('Ha ocurrido algún problema al procesar la trama recibida')
            return False

        logger.info (res)
        res = self.enviaMensajeServidor ('enviaArchivo', { 'nfl': res['nfl'] })
        if (type (res) is not dict):
            logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración')
            logger.error ('Ha ocurrido algún problema al recibir un archivo por la red')
            return False
        logger.debug (f'res ({res})')

        fileautoexec = '/tmp/_autoexec_{}'.format (self.IPlocal)
        logger.debug ('fileautoexec ({})'.format (fileautoexec))
        with open (fileautoexec, 'w') as fd:
            fd.write (base64.b64decode (res['contents']).decode ('utf-8'))

        self.ejecutaArchivo (fileautoexec)

        return True

    def comandosPendientes (self):
        while (True):
            res = self.enviaMensajeServidor ('ComandosPendientes')   ## receives just one command
            if (type (res) is not dict):
                logger.error ('Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración')
                logger.error ('Ha ocurrido algún problema al recibir una petición de comandos o tareas pendientes desde el Servidor de Administración')
                return False

            logger.info (res)
            if ('NoComandosPtes' == res['nfn']):
                break

            ## TODO manage the rest of cases... we might have to do something similar to ejecutaArchivo
            #if (!gestionaTrama (ptrTrama)){   // Análisis de la trama
            #    logger.error ('Ha ocurrido algún problema al procesar la trama recibida')
            #    return False
            #}
            ## ATM let's just return false to avoid a possible infinite loop
            return False

        return True

    def procesaComandos (self):
        res = self.enviaMensajeServidor ('DisponibilidadComandos', { 'tpc': 'OPG' })     ## Activar disponibilidad
        logger.debug ('res ({})'.format (res))

        if (type (res) is not dict):
            logger.error ('Ha ocurrido algún problema al enviar una petición de comandos interactivos al Servidor de Administración')
            return False

        logger.info ('Disponibilidad de comandos activada')     ## Disponibilidad de cliente activada

        ## we now return true and the outer agent code gets to wait for requests from outside
        ## TODO thing is, ogAdmClient always calls comandosPendientes() after every received request. How do we do that here?
        #
        #ptrTrama=recibeMensaje (&socket_c);
        #if (!ptrTrama){
        #    errorLog (modulo,46,FALSE);     'Ha ocurrido algún problema al recibir un comando interactivo desde el Servidor de Administración'
        #    return;
        #}
        #close (socket_c);
        #if (!gestionaTrama (ptrTrama)){   // Análisis de la trama
        #    errorLog (modulo,39,FALSE);     'Ha ocurrido algún problema al procesar la trama recibida'
        #    return;
        #}
        #if (!comandosPendientes (ptrTrama)){
        #    errorLog (modulo,42,FALSE);     'Ha ocurrido algún problema al enviar una petición de comandos o tareas pendientes al Servidor de Administración'
        #}

    def onActivation (self):
        super().onActivation()
        self.exec_level = 'full'
        self.random = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(32))

        try:
            logger.info ('Inicio de sesion')
            logger.info ('Abriendo sesión en el servidor de Administración')
            if (not self.inclusionCliente()):
                raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')

            logger.info ('Cliente iniciado')
            logger.info ('Procesando caché')
            if not self.cuestionCache():
                raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')

            if self.idproautoexec > 0:
                logger.info ('Ejecución de archivo Autoexec')
                if not self.autoexecCliente():
                    raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')

            logger.info ('Procesa comandos pendientes')
            if not self.comandosPendientes():
                raise Exception ('Se han generado errores. No se puede continuar la ejecución de este módulo')

            logger.info ('Acciones pendientes procesadas')
        except:
            self.muestraError()
            logger.warning ('onActivation failed')
            return

        self.muestraMenu()
        self.procesaComandos()

        logger.info ('onActivation ok')

    def onDeactivation (self):
        """
        Sends OGAgent stopping notification to OpenGnsys server
        """
        logger.debug ('onDeactivation')
        self.REST.sendMessage ('ogagent/stopped', {'mac': self.mac, 'ip': self.IPlocal, 'idcentro': self.idcentro, 'idaula': self.idaula,
                                                   'idordenador': self.idordenador, 'nombreordenador': self.nombreordenador})









    def do_CrearImagen (self, post_params):
        for k in ['dsk', 'par', 'cpt', 'idi', 'nci', 'ipr', 'nfn', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        dsk = post_params['dsk'] ## Disco
        par = post_params['par'] ## Número de partición
        cpt = post_params['cpt'] ## Código de la partición
        idi = post_params['idi'] ## Identificador de la imagen
        nci = post_params['nci'] ## Nombre canónico de la imagen
        ipr = post_params['ipr'] ## Ip del repositorio
        nfn = post_params['nfn']
        ids = post_params['ids']

        self.muestraMensaje (7)

        try:
            res = self.InventariandoSoftware (dsk, par, 'InventarioSoftware')   ## Crea inventario Software previamente
        except:
            logger.warning ('Error al ejecutar el comando')
            return {}

        if res['contents']:
            self.muestraMensaje (2)
            inv_sft = res['contents']
            try:
                self.interfaceAdmin (nfn, [dsk, par, nci, ipr])
                self.muestraMensaje (9)
                herror = 0
            except:
                logger.warning ('Error al ejecutar el comando')
                self.muestraMensaje (10)
                herror = 1
        else:
            logger.warning ('Error al ejecutar el comando')
            herror = 1
            inv_sft = ''

        self.muestraMenu()

        cmd = {
            'nfn':     'RESPUESTA_CrearImagen',
            'idi':     idi,    ## Identificador de la imagen
            'dsk':     dsk,    ## Número de disco
            'par':     par,    ## Número de partición de donde se creó
            'cpt':     cpt,    ## Tipo o código de partición
            'ipr':     ipr,    ## Ip del repositorio donde se alojó
            'inv_sft': inv_sft,
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_CrearImagenGit (self, post_params):
        for k in ['nfn', 'ids', 'disk', 'partition', 'repository', 'image_name']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        ids = post_params['ids']

        disk       = post_params['disk']        ## Numero de disco
        partition  = post_params['partition']   ## Numero de partición
        repo       = post_params['repository']  ## URL a repositorio
        image_name = post_params['image_name']  ## Nombre de imagen

        msg        = post_params['message']     ## Mensaje de commit (opcional)


        self.muestraMensaje (7)

        try:
            res = self.InventariandoSoftware (disk, partition, 'InventarioSoftware')   ## Crea inventario Software previamente
        except:
            logger.warning ('Error al ejecutar el comando')
            return {}

        if res['contents']:
            self.muestraMensaje (2)
            inv_sft = res['contents']
            try:
                create_args = ["--disk", disk, "--partition", partition, "--repository", repo, "--image-name", image_name]
                if msg:
                    create_args = create_args + ['--message', msg]

                self.interfaceAdmin (nfn, create_args)
                self.muestraMensaje (9)
                herror = 0
            except:
                logger.warning ('Error al ejecutar el comando')
                self.muestraMensaje (10)
                herror = 1
        else:
            logger.warning ('Error al ejecutar el comando')
            herror = 1
            inv_sft = ''

        self.muestraMenu()

        cmd = {
            'nfn':     'RESPUESTA_CrearImagenGit',
            'disk'     : disk,         ## Número de disco
            'partition': partition,    ## Número de partición de donde se creó
            'repo'     : repo,         ## Ip del repositorio donde se alojó
            'inv_sft'  : inv_sft
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)


    def do_ModificarImagenGit (self, post_params):
        for k in ['nfn', 'ids', 'disk', 'partition', 'repository', 'branch', 'options', 'message']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        ids = post_params['ids']

        disk       = post_params['disk']         ## Disco
        partition  = post_params['partition']    ## Número de partición
        repo       = post_params['repository']   ## Ip del repositorio

        branch     = post_params['branch']       ## Rama nueva a crear
        options    = post_params['options']      ## Opciones (force push, etc)
        msg        = post_params['message']      ## Commit message

        self.muestraMensaje (7)

        try:
            res = self.InventariandoSoftware (disk, partition, 'InventarioSoftware')   ## Crea inventario Software previamente
        except:
            logger.warning ('Error al ejecutar el comando')
            return {}

        if res['contents']:
            self.muestraMensaje (2)
            inv_sft = res['contents']
            try:
                mod_args = ["--disk", disk, "--partition", partition, "--repository", repo, "--branch", branch, "--message", msg]
                if options:
                    mod_args = mod_args + ['--options', options]

                self.interfaceAdmin (nfn, mod_args)
                self.muestraMensaje (9)
                herror = 0
            except:
                logger.warning ('Error al ejecutar el comando')
                self.muestraMensaje (10)
                herror = 1
        else:
            logger.warning ('Error al ejecutar el comando')
            herror = 1
            inv_sft = ''

        self.muestraMenu()

        cmd = {
            'nfn':     'RESPUESTA_ModificarImagenGit',
            'disk'      : disk,         ## Número de disco
            'partition' : partition,    ## Número de partición de donde se creó
            'repo'      : repo,         ## Ip del repositorio donde se alojó
            'branch'    : branch,       ## Rama creada
            'inv_sft'   : inv_sft
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)


    def do_RestaurarImagen (self, post_params):
        for k in ['dsk', 'par', 'idi', 'ipr', 'nci', 'ifs', 'ptc', 'nfn', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        dsk = post_params['dsk']
        par = post_params['par']
        idi = post_params['idi']
        ipr = post_params['ipr']
        nci = post_params['nci']
        ifs = post_params['ifs']
        ptc = post_params['ptc']    ## Protocolo de clonación: Unicast, Multicast, Torrent
        nfn = post_params['nfn']
        ids = post_params['ids']

        self.muestraMensaje (3)

        try:
            ## the ptc.split() is useless right now, since interfaceAdmin() does ' '.join(params) in order to spawn a shell
            ## however we're going to need it in the future (when everything gets translated into python), plus it's harmless now. So let's do it
            #self.interfaceAdmin (nfn, [dsk, par, nci, ipr, ptc])
            self.interfaceAdmin (nfn, [dsk, par, nci, ipr] + ptc.split())
            self.muestraMensaje (11)
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            self.muestraMensaje (12)
            herror = 1

        cfg = self.LeeConfiguracion()
        if not cfg:
            logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')

        self.muestraMenu()

        cmd = {
            'nfn': 'RESPUESTA_RestaurarImagen',
            'idi': idi,                  ## Identificador de la imagen
            'dsk': dsk,                  ## Número de disco
            'par': par,                  ## Número de partición
            'ifs': ifs,                  ## Identificador del perfil software
            'cfg': self.cfg2obj(cfg),    ## Configuración de discos
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_RestaurarImagenGit (self, post_params):
        for k in ['nfn', 'ids', 'disk', 'partition', 'repository', 'image_name', 'commit']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn       = post_params['nfn']
        ids       = post_params['ids']

        disk        = post_params['disk']          ## Numero de disco
        partition   = post_params['partition']     ## Numero de partición
        repo        = post_params['repository']    ## URL a repositorio
        image_name  = post_params['image_name']   ## Nombre de imagen

        branch      = post_params['branch']        ## Rama de git seleccionada para restaurar
        commit      = post_params['commit']        ## Referencia de git a restaurar. Debe ser un commit dentro de la rama indicada.

        self.muestraMensaje (3)

        try:
            restore_args = ["--disk", disk, "--partition", partition, "--repository", repo, "--image-name", image_name, "--commit", commit]
            if branch:
                restore_args = restore_args + ["--branch", branch]

            self.interfaceAdmin (nfn, restore_args)
            self.muestraMensaje (11)
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            self.muestraMensaje (12)
            herror = 1

        cfg = self.LeeConfiguracion()
        if not cfg:
            logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')

        self.muestraMenu()

        cmd = {
            'nfn': 'RESPUESTA_RestaurarImagenGit',
            'disk'     : disk,           ## Número de disco
            'partition': partition,      ## Número de partición
            'repo'     : repo,           ## Repositorio de git
            'cfg': self.cfg2obj(cfg),    ## Configuración de discos
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)


    def do_Configurar (self, post_params):
        for k in ['nfn', 'dsk', 'cfg', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        dsk = post_params['dsk']
        cfg = post_params['cfg']
        ids = post_params['ids']
        check_sizes = str ('check-sizes' in post_params and 'true' == post_params['check-sizes']).lower()
        gen_script = str ('gen-script' in post_params and 'true' == post_params['gen-script']).lower()

        if 'true' != check_sizes: self.muestraMensaje (4)

        params = []
        disk_info = cfg.pop (0)
        logger.debug (f'disk_info ({disk_info})')
        for k in ['dis']:
            params.append (f'{k}={disk_info[k]}')
        disk_info_str = '*'.join (params)

        partitions = []
        for entry in cfg:
            logger.debug (f'entry ({entry})')
            params = []
            for k in ['par', 'cpt', 'sfi', 'tam', 'ope']:
                params.append (f'{k}={entry[k]}')
            partitions.append ('*'.join (params))
        part_info_str = '%'.join (partitions)

        cfg_str = f'{disk_info_str}!{part_info_str}%'

        script_out = f'/tmp/ConfigurarScript-{self.IPlocal}'
        try:
            self.interfaceAdmin (nfn, ['ignored', cfg_str, script_out, check_sizes, gen_script])
            if 'true' != check_sizes: self.muestraMensaje (14)
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            if 'true' != check_sizes: self.muestraMensaje (13)
            herror = 1

        cmd = {
            'nfn': 'RESPUESTA_Configurar',
        }

        if 'true' == gen_script:
            script_contents = Path (script_out).read_bytes()
            cmd['script'] = base64.b64encode (script_contents).decode ('utf-8')

        ## si check_sizes y gen_script son falsos, entonces hemos reparticionado el disco: leer configuración de nuevo
        if 'true' != check_sizes and 'true' != gen_script:
            cfg = self.LeeConfiguracion()
            if not cfg:
                logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
                return {}
            cmd['cfg'] = self.cfg2obj (cfg)

        self.muestraMenu()
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_InventarioHardware (self, post_params):
        self.muestraMensaje (6)

        hrdsrc = f'/tmp/Chrd-{self.IPlocal}'      ## Nombre que tendra el archivo de inventario
        try:
            self.interfaceAdmin ('InventarioHardware', [hrdsrc])
            hrdsrc_contents = Path (hrdsrc).read_bytes()
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            self.muestraMensaje (18)
            herror = 1

        cmd = {}

        if not herror:
            cmd['hrd'] = base64.b64encode (hrdsrc_contents).decode ('utf-8')

        self.muestraMenu()
        return self.respuestaEjecucionComando (cmd, herror)

    def do_InventarioSoftware (self, post_params):
        for k in ['nfn', 'dsk', 'par', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        dsk = post_params['dsk']
        par = post_params['par']
        ids = post_params['ids']

        self.muestraMensaje (7)

        try:
            cmd = self.InventariandoSoftware (dsk, par, 'InventarioSoftware')
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            cmd = { 'nfn': 'RESPUESTA_InventarioSoftware' }
            herror = 1

        self.muestraMenu()
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_Actualizar (self, post_params):
        self.muestraMensaje (1)
        #if !comandosPendientes: error 84 'Ha ocurrido algún problema al reiniciar la sesión del cliente'
        cfg = self.LeeConfiguracion()
        if not cfg:
            logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
            logger.error ('LeeConfiguracion() failed')
            return {}

        cmd = {
            'nfn': 'RESPUESTA_Actualizar',
            'cfg': self.cfg2obj (cfg),
        }
        self.muestraMenu()
        return self.respuestaEjecucionComando (cmd, 0)

    def do_Comando (self, post_params):
        for k in ['nfn', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        ids = post_params['ids']

        try:
            self.interfaceAdmin (nfn)
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            herror = 1

        cmd = {
            'nfn': 'RESPUESTA_Comando',
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_ConsolaRemota (self, post_params):
        for k in ['nfn', 'scp']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        scp = base64.b64decode (unquote (post_params['scp'])).decode ('utf-8')
        filescript = f'/tmp/_script_{self.IPlocal}'
        ecosrc = f'/tmp/_econsola_{self.IPlocal}'
        ecodst = f'/tmp/_Seconsola_{self.IPlocal}'    ## Nombre que tendra el archivo en el Servidor

        with open (filescript, 'w') as fd:
            fd.write (scp)

        try:
            self.interfaceAdmin (nfn, [filescript, ecosrc])
            ecosrc_contents = Path (ecosrc).read_bytes()
        except:
            logger.error ('Error al ejecutar el comando')
            return {}

        logger.debug ('sending recibeArchivo to server')
        res = self.enviaMensajeServidor ('recibeArchivo', { 'nfl': ecodst, 'contents': base64.b64encode (ecosrc_contents).decode ('utf-8') })
        logger.debug (res)
        if not res:
            logger.error ('Ha ocurrido algún problema al enviar un archivo por la red')

        return {}

    def do_Apagar (self, post_params):
        for k in ['nfn', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        ids = post_params['ids']

        try:
            self.interfaceAdmin (nfn)
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            herror = 1

        cmd = {
            'nfn': 'RESPUESTA_Apagar',
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_Reiniciar (self, post_params):
        for k in ['nfn', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        ids = post_params['ids']

        try:
            self.interfaceAdmin (nfn)
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            herror = 1

        cmd = {
            'nfn': 'RESPUESTA_Reiniciar',
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_IniciarSesion (self, post_params):
        for k in ['nfn', 'dsk', 'par', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        dsk = post_params['dsk']
        par = post_params['par']
        ids = post_params['ids']

        try:
            self.interfaceAdmin (nfn, [dsk, par])
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            herror = 1

        cmd = {
            'nfn': 'RESPUESTA_IniciarSesion',
        }
        return self.respuestaEjecucionComando (cmd, herror, ids)

    def do_EjecutarScript (self, post_params):
        for k in ['nfn', 'scp', 'ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        nfn = post_params['nfn']
        scp = base64.b64decode (unquote (post_params['scp'])).decode ('utf-8')
        ids = post_params['ids']

        self.muestraMensaje (8)

        filescript = f'/tmp/_script_{self.IPlocal}'     ## Nombre del archivo de script
        with open (filescript, 'w') as fd:
            fd.write (scp)

        try:
            self.interfaceAdmin (nfn, [filescript])
            self.muestraMensaje (22)
            herror = 0
        except:
            logger.warning ('Error al ejecutar el comando')
            self.muestraMensaje (21)
            herror = 1

        ## Toma configuración de particiones
        cfg = self.LeeConfiguracion()
        if not cfg:
            logger.warning ('No se ha podido recuperar la configuración de las particiones del disco')
            herror = 36

        #herror=ejecutarCodigoBash(scp);       ## ogAdmClient.c:2004

        cmd = {
            'nfn': 'RESPUESTA_EjecutarScript',
            'cfg': self.cfg2obj (cfg),
        }
        self.muestraMenu()
        return self.respuestaEjecucionComando (cmd, herror, ids)





    @execution_level('status')
    def process_status (self, path, get_params, post_params, server):
        logger.debug ('in process_status, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        full_config = 'full-config' in post_params and post_params['full-config']
        thr_status = {}
        for k in self.thread_list:
            thr_status[k] = {
                'running': self.thread_list[k]['running'],
                'result': self.thread_list[k]['result'],
            }
        ret = {
            'nfn': 'RESPUESTA_status',
            'mac': self.mac,
            'st': 'OGL',
            'ip': self.IPlocal,
            'threads': thr_status,
        }
        if full_config:
            cfg = self.LeeConfiguracion()
            ret['cfg'] = self.cfg2obj (cfg)
        return ret

    @check_secret
    def process_popup (self, path, get_params, post_params, server):
        logger.debug ('in process_popup, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.debug ('type(post_params) "{}"'.format (type (post_params)))
        ## in process_popup, should not happen, path "[]" get_params "{}" post_params "{'title': 'mi titulo', 'message': 'mi mensaje'}" server "<opengnsys.httpserver.HTTPServerHandler object at 0x7fa788cb8fa0>"
        ## type(post_params) "<class 'dict'>"
        return {'debug':'test'}

    @execution_level('full')
    @check_secret
    def process_Actualizar (self, path, get_params, post_params, server):
        logger.debug ('in process_Actualizar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('Actualizar', self.do_Actualizar, args=(post_params,))

    @execution_level('full')
    @check_secret
    def process_Purgar (self, path, get_params, post_params, server):
        logger.debug ('in process_Purgar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        os.kill (os.getpid(), signal.SIGTERM)
        return {}
        #exit (0)      ## ogAdmClient.c:905

    @execution_level('full')
    @check_secret
    def process_Comando (self, path, get_params, post_params, server):
        logger.debug ('in process_Comando, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('Comando', self.do_Comando, args=(post_params,))

    def process_Sondeo (self, path, get_params, post_params, server):
        logger.debug ('in process_Sondeo, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return {}      ## ogAdmClient.c:920

    @execution_level('full')
    @check_secret
    def process_ConsolaRemota (self, path, get_params, post_params, server):
        logger.debug ('in process_ConsolaRemota, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('ConsolaRemota', self.do_ConsolaRemota, args=(post_params,))

    @execution_level('full')
    @check_secret
    def process_Arrancar (self, path, get_params, post_params, server):
        logger.debug ('in process_Arrancar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}

        for k in ['ids']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        ids = post_params['ids']

        cmd = {
            'nfn': 'RESPUESTA_Arrancar',
            'tpc': 'OPG',
        }
        return self.respuestaEjecucionComando (cmd, 0, ids)

    @execution_level('halt')
    @check_secret
    def process_Apagar (self, path, get_params, post_params, server):
        logger.debug ('in process_Apagar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('Apagar', self.do_Apagar, args=(post_params,))

    @execution_level('halt')
    @check_secret
    def process_Reiniciar (self, path, get_params, post_params, server):
        logger.debug ('in process_Reiniciar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('Reiniciar', self.do_Reiniciar, args=(post_params,))

    @execution_level('full')
    @check_secret
    def process_IniciarSesion (self, path, get_params, post_params, server):
        logger.debug ('in process_IniciarSesion, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('IniciarSesion', self.do_IniciarSesion, args=(post_params,))

    @execution_level('full')
    @check_secret
    def process_EjecutarScript (self, path, get_params, post_params, server):
        logger.debug ('in process_EjecutarScript, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('EjecutarScript', self.do_EjecutarScript, args=(post_params,))

    @execution_level('full')
    @check_secret
    def process_EjecutaComandosPendientes (self, path, get_params, post_params, server):
        logger.debug ('in process_EjecutaComandosPendientes, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return {'true':'true'}         ## ogAdmClient.c:2138

    @execution_level('full')
    @check_secret
    def process_CrearImagen (self, path, get_params, post_params, server):
        logger.debug ('in process_CrearImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.debug ('type(post_params) "{}"'.format (type (post_params)))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('CrearImagen', self.do_CrearImagen, args=(post_params,))

    def process_CrearImagenGit (self, path, get_params, post_params, server):
        logger.debug ('in process_CrearImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.debug ('type(post_params) "{}"'.format (type (post_params)))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('CrearImagenGit', self.do_CrearImagenGit, args=(post_params,))

    def process_ModificarImagenGit (self, path, get_params, post_params, server):
        logger.debug ('in process_ModificarImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.debug ('type(post_params) "{}"'.format (type (post_params)))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('ModificarImagenGit', self.do_ModificarImagenGit, args=(post_params,))

    def process_RestaurarImagenGit (self, path, get_params, post_params, server):
        logger.debug ('in process_RestaurarImagenGit, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.debug ('type(post_params) "{}"'.format (type (post_params)))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('RestaurarImagenGit', self.do_RestaurarImagenGit, args=(post_params,))

    @execution_level('full')
    @check_secret
    def process_RestaurarImagen (self, path, get_params, post_params, server):
        logger.debug ('in process_RestaurarImagen, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        logger.debug ('type(post_params) "{}"'.format (type (post_params)))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('RestaurarImagen', self.do_RestaurarImagen, args=(post_params,))

    @execution_level('full')
    @check_secret
    def process_Configurar (self, path, get_params, post_params, server):
        logger.debug ('in process_Configurar, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}

        check_sizes = str ('check-sizes' in post_params and 'true' == post_params['check-sizes']).lower()
        gen_script = str ('gen-script' in post_params and 'true' == post_params['gen-script']).lower()
        if 'true' == check_sizes or 'true' == gen_script:
            return self.do_Configurar (post_params)

        return self._long_running_job ('Configurar', self.do_Configurar, args=(post_params,))

    @execution_level('full')
    @check_secret
    def process_InventarioHardware (self, path, get_params, post_params, server):
        logger.debug ('in process_InventarioHardware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self.do_InventarioHardware (post_params)

    @execution_level('full')
    @check_secret
    def process_InventarioSoftware (self, path, get_params, post_params, server):
        logger.debug ('in process_InventarioSoftware, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        return self._long_running_job ('InventarioSoftware', self.do_InventarioSoftware, args=(post_params,))

    @execution_level('full')
    @check_secret
    def process_KillJob (self, path, get_params, post_params, server):
        logger.debug ('in process_KillJob, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}
        jid = post_params['job_id']
        r = self.killer (jid)
        logger.debug (f'r bef ({r})')
        r.update ({ 'nfn':'RESPUESTA_KillJob', 'job':jid })
        logger.debug (f'r aft ({r})')
        return r

    @execution_level('full')
    @check_secret
    def process_GetGitData (self, path, get_params, post_params, server):
        logger.debug ('in process_GetGitData, path "{}" get_params "{}" post_params "{}" server "{}"'.format (path, get_params, post_params, server))
        if post_params is None:
            logger.warning ('POST body is empty--setting it to an empty dictionary')
            post_params = {}

        for k in ['nfn', 'dsk', 'par', 'nfn']:
            if k not in post_params:
                logger.error (f'required parameter ({k}) not in POST params')
                return {}

        tmp_gitdata = f'/tmp/gitdata-{self.IPlocal}'
        nfn = post_params['nfn']
        dsk = post_params['dsk']
        par = post_params['par']

        try:
            self.interfaceAdmin (nfn, [dsk, par, tmp_gitdata])
            herror = 0
        except:
            herror = 1

        if not os.path.exists (tmp_gitdata):
            return self.respuestaEjecucionComando ({'nfn':'RESPUESTA_GetGitData'}, 1)

        with open (tmp_gitdata, 'r') as fd:
            gitdata = fd.read().strip()

        branch, repo = gitdata.split (':')
        cmd = {
            'nfn':    'RESPUESTA_GetGitData',
            'branch': branch,
            'repo':   repo,
        }
        return self.respuestaEjecucionComando (cmd, herror)
