#!/bin/bash

#/**
#@file    oglivecli
#@brief   Command line tool to manage ogLive clients.
#@usage   oglivecli Command [Options ...]
#@param   Command:
#@param     help                      show this help
#@param     config [Parameter]        show configuration parameters
#@param     disk_usage                show disk usage information
#@param     list_installed_oglives    list installed ogLive clients
#@param     check_services_status     check status of critical services
#@param     download                  show a menu to download an ogLive ISO image from the OpenGnsys website
#@param     download Iso              download a specific ogLive ISO image from the OpenGnsys website
#@param     install Iso               install a new ogLive client from a downloaded ISO image
#@param     uninstall uuid            uninstall an ogLive client using its UUID
#@param     get_default               get index value for default ogLive client
#@param     set_default uuid          set default ogLive client using its UUID
#@param     get_info uuid             get JSON information about an installed ogLive client using its UUID
#@warning This script needs "jq" command.
#@version 3.0.0 - Updated with to adapt to Ogboot.
#@author  Ramón M. Gómez - ETSII Univ. Sevilla
#@author  Qindel formacion y servicios SL
#@date    2024-08-06
#*/ ##


# Global constants definition.
PROG=$(basename "$(realpath "$0")")			# Program name.
PROGDIR=$(dirname "$(realpath "$0")")			# Dir where program is
OPENGNSYS=$(realpath $PROGDIR/../)				# ogboot main directory.
DOWNLOADDIR=$OPENGNSYS/lib				# Directory to store ogLive images.
#@DOWNLOADURL="https://ognproject.evlt.uma.es/trac/downloads"	# Download URL.
DOWNLOADURL="https://ognproject.evlt.uma.es/oglive/"
TFTPDIR=$OPENGNSYS/tftpboot/				# TFTP directory.
DEFOGLIVE="ogLive"					# Default ogLive directory.
MINREL=20190601						# Mininum ogLive compatibility release.
INFOFILE=$OPENGNSYS/etc/ogliveinfo.json			# Configuration file.


# Global and secondary functions.

source $OPENGNSYS/lib/ogfunctions.sh || exit 1



# Check consistency, showing configuration problems.
function check() {
    local ERR=0 AUX INST DEF
    [ $# -ne 0 ] && raiseError usage
    # Check for old system that needs conversion.
    if [ -z "$(stat -c "%N" $TFTPDIR/ogclient | awk '$3~/'$DEFOGLIVE'/ {print}')" ]; then
         echo "This server uses old ogclient, please run \"$PROG convert\" to update."
         let ERR++
         [ ! -f $INFOFILE ] && return $ERR
    fi
    # Check for other problems.
    [ ! -f $INFOFILE ] && echo "Configuration file does not exists: $INFOFILE" && let ERR++
    [ -f $INFOFILE -a "$(jq -c keys $INFOFILE 2>/dev/null)" != "[\"default\",\"oglive\"]" ] && echo "Format error in configuration file: $INFOFILE" && let ERR++
    [ ! -e $TFTPDIR ] && echo "TFTP directory does not exist: $TFTPDIR." && let ERR++
    # Check for installed ogLive clients.
    INST=( $(find $TFTPDIR/ -type d -name "$DEFOGLIVE-*" -a ! -name "*.old" -printf "%f\n" | sort) )
    [[ ${#INST[@]} -eq 0 ]] && echo "No ogLive clients are installed." && let ERR++
    DEF=( $(jq -r .oglive[].directory $INFOFILE 2>/dev/null | sort) )
    # Compare installed and defined ogLive clients.
    AUX=$(comm -23 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]}))
    [ -n "$AUX" ] && echo "Some ogLive are installed but not defined: ${AUX//$'\n'/, }" && let ERR++
    AUX=$(comm -13 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]}))
    [ -n "$AUX" ] && echo "Some ogLive are defined but not installed: ${AUX//$'\n'/, }" && let ERR++
    # Compare downloaded and defined ISO images.
    INST=( $(find $DOWNLOADDIR/ -type f -name "$DEFOGLIVE-*.iso" -printf "%f\n" | sort) )
    DEF=( $(jq -r .oglive[].iso $INFOFILE 2>/dev/null | sort) )
    AUX=$(comm -23 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]}))
    [ -n "$AUX" ] && echo "Some ISOs are downloaded but not defined: ${AUX//$'\n'/, }" && let ERR++
    AUX=$(comm -13 <(printf "%s\n" ${INST[*]}) <(printf "%s\n" ${DEF[*]}))
    [ -n "$AUX" ] && echo "Some ISOs are defined but not downloaded: ${AUX//$'\n'/, }" && let ERR++
    # Check for new ISO files downloaded after installation.
    AUX=$(jq -r '.oglive[] as $og | $og.iso + ":" + $og.directory' $INFOFILE 2>/dev/null | \
            while IFS=":" read -r DEF INST; do
                [ $DOWNLOADDIR/$DEF -nt $TFTPDIR/$INST ] && echo "$DEF"
            done)
    [ -n "$AUX" ] && echo "Some ISOs are downloaded after installation: ${AUX//$'\n'/, }" && let ERR++
    AUX=$(jq -r '.oglive[] as $og | if ($og.revision[1:9] | tonumber) < '$MINREL' then $og.directory else "" end' $INFOFILE 2>/dev/null)
    [ -n "$AUX" ] && echo "Some installed ogLive aren't fully compatible: ${AUX//$'\n'/, }" && let ERR++
    DEF=$(jq -r ".oglive[$(getdefault)].directory" $INFOFILE 2>/dev/null)
    INST=$(stat -c "%N" $TFTPDIR/$DEFOGLIVE | cut -f4 -d\')
    [ "$DEF" != "$INST" ] && echo "Default ogLive is not linked to right directory: $DEF <> $INST" && let ERR++
    # Print result.
    [ $ERR -eq 0 ] && echo "OK!" || echo "Problems detected: $ERR"
    return $ERR
}

# List installed ogLive clients.
function list() {
    [ $# -ne 0 ] && raiseError usage
    [ ! -r $INFOFILE ] && raiseError access "Configuration file."
    # List all defined indexes, directories and check if missing.
    jq -r .oglive[].directory $INFOFILE | nl -v 0 | \
            awk '{system("echo -n "$0"; test -d '$TFTPDIR'/"$2" || echo -n \" (missing)\"; echo")}' | column -t
}

# Show information about an installed ogLive client.
function show() {
    local INDEX
    [ $# -ne 1 ] && raiseError usage
    [ ! -r $INFOFILE ] && raiseError access "Configuration file."
    # Show JSON entries.
    case "$1" in
        default)    # Default index.
            INDEX="[$(jq -r .default $INFOFILE)]" ;;
        all)        # All intries.
            ;;
        [0-9]*)     # Index.
            INDEX="[$1]" ;;
        *)          # Directory.
            INDEX="[$(search "$1" 2>/dev/null)]" || raiseError notfound "Directory \"$1\"."
            ;;
    esac
    jq ".oglive$INDEX" $INFOFILE || raiseError notfound "Index \"$1\"."
}

# Show index or directory corresponding to searching parameter.
function search() {
    [ $# -ne 1 ] && raiseError usage
    [ ! -r $INFOFILE ] && raiseError access "Configuration file."
    # Show corresponding index or directory.
    list | awk -v d="$1" '{if ($2==d) print $1; if ($1==d) print $2}' | grep . || raiseError notfound "Index/Directory \"$1\"."
}
function download() {
    local OGLIVEFILE TARGETFILE SOURCELENGTH

    # Verificar si el directorio de descarga existe y tiene permisos de escritura.
    [ ! -d "$DOWNLOADDIR" ] && raiseError notfound "Directorio de descarga."
    [ ! -w "$DOWNLOADDIR" ] && raiseError access "Directorio de descarga."

    # Si no se proporciona ningún parámetro, mostrar el menú de descargas.
    if [ -z "$1" ]; then
        downloadMenu
    else
        local download_url="$1"
        OGLIVEFILE=$(basename "$download_url")
        local oglive_name="${OGLIVEFILE%.iso}" # Nombre del ogLive sin la extensión .iso
        INSTALLED_PATH="$TFTPDIR/$oglive_name" # Ruta de instalación donde se verifica

        # Validar que la URL apunte a un archivo ISO.
        if [[ ! "$OGLIVEFILE" =~ \.iso$ ]]; then
            raiseError download "La URL no apunta a un archivo ISO."
        fi

        # Obtener el tamaño de descarga.
        SOURCELENGTH=$(curl -k --head --retry 5 --retry-delay 5 --max-time 30 "$download_url" | awk -F: '/Content-Length:/ {print $2}')
        [ -n "$SOURCELENGTH" ] || raiseError download "No se pudo obtener el tamaño del archivo desde \"$download_url\"."
        
        # Verificar si el ogLive ya está instalado
        if [ -d "$INSTALLED_PATH" ]; then
            raiseError download "El ogLive \"$oglive_name\" ya está instalado en \"$INSTALLED_PATH\"."
        fi

        # Descargar ogLive.
        TARGETFILE="$DOWNLOADDIR/$OGLIVEFILE"
        trap "rm -f $TARGETFILE" 1 2 3 6 9 15
        curl -k --retry 5 --retry-delay 5 "$download_url" -o "$TARGETFILE" || raiseError download "No se pudo descargar \"$OGLIVEFILE\"."
        if [ -f "$TARGETFILE" ]; then
            local file_type=$(file -b "$TARGETFILE")
            if [[ "$file_type" =~ "ISO 9660" ]] && [[ "$file_type" =~ "ogClient" ]]; then
	             install "$OGLIVEFILE"
            else
                raiseError download "El archivo descargado no es un ogLive ISO válido."
            fi
        else
            raiseError download "El archivo no fue descargado correctamente."
        fi

    fi
}

# Muestra un menú para seleccionar y descargar un archivo ogLive ISO del sitio web de OpenGnsys.
function downloadMenu() {
    local OGLIVE NISOS i HTTPCODE ISOREL
    # Usamos grep para filtrar solo los enlaces que contienen archivos ISO, pero eliminamos etiquetas HTML con sed
    OGLIVE=( $(curl -k --silent "$DOWNLOADURL" | grep -oP '(?<=href=")[^"]*\.iso' | sed 's/^.*\///') )
    NISOS=${#OGLIVE[@]}

    local downloads=()

    for i in $(seq 1 $NISOS); do
        installed=false
        compatible=false

        # Obtener el nombre completo del archivo ISO
        OGLIVEFILE="${OGLIVE[i-1]}"

        # Extraer la distribución, revisión y arquitectura
        OGLIVEDIST="$(echo $OGLIVEFILE | cut -f2 -d-)"
        OGLIVEREV="${OGLIVEFILE##*-}"; OGLIVEREV="${OGLIVEREV%%.*}"
        OGLIVEKRNL="$(echo $OGLIVEFILE | cut -f3- -d-)"; OGLIVEKRNL="${OGLIVEKRNL%-$OGLIVEREV.*}"
        OGLIVEARCH="$(echo $OGLIVEFILE | awk -F- '{print $(NF-1)}')"

        case "$OGLIVEARCH" in
            i386|amd64)
                OGLIVEKRNL="${OGLIVEKRNL%-$OGLIVEARCH}" ;;
            *)
                OGLIVEARCH="i386" ;;
        esac

        OGLIVEDIR="$TFTPDIR/$DEFOGLIVE-${OGLIVEKRNL%%-*}-$OGLIVEARCH-$OGLIVEREV"
        OGLIVEDIR="${OGLIVEDIR/amd64-/}"

        # Verificar si el ogLive está instalado y no es un directorio .old
        if [ -d "$OGLIVEDIR" ] && [[ ! "$OGLIVEDIR" =~ \.old$ ]]; then
            installed=true
        else
            installed=false
        fi

        ISOREL=${OGLIVE[i-1]##*-r}; ISOREL=${ISOREL%%.*}
        [ "$ISOREL" -ge "$MINREL" ] && compatible=true

        url="$DOWNLOADURL/${OGLIVE[i-1]}"

        local DATA=$(jq -n \
            --arg id "$i" \
            --arg filename "$OGLIVEFILE" \
            --arg url "$url" \
            --argjson installed "$installed" \
            --argjson compatible "$compatible" \
            '{id: $id, filename: $filename, url: $url, installed: $installed, compatible: $compatible}')

        downloads+=("$DATA")
    done

    jq -n --argjson downloads "$(printf '%s\n' "${downloads[@]}" | jq -s .)" \
        '{downloads: $downloads}'
}


# Show a menu to select and download an ogLive ISO image from the OpenGnsys website.
function download_old() {
    local OGLIVE NISOS i HTTPCODE TARGETFILE
    local ISOREL
    [ $# -gt 1 ] && raiseError usage
    [ ! -d $DOWNLOADDIR ] && raiseError notfound "Download directory."
    [ ! -w $DOWNLOADDIR ] && raiseError access "Download directory."
    # Check parameter.
    if [ -n "$1" ]; then
        # ogLive to download.
        OGLIVEFILE="$1"
    else
        # Show download menu.
        OGLIVE=( $(curl -k --silent $DOWNLOADURL | grep "$DEFOGLIVE.*iso") )
        NISOS=${#OGLIVE[@]}
        echo "Available downloads (+ = installed, * = full compatibility):"
        for i in $(seq 1 $NISOS); do
            [ -e $DOWNLOADDIR/${OGLIVE[i-1]} ] && OGLIVE[i-1]="(+) ${OGLIVE[i-1]}"
            ISOREL=${OGLIVE[i-1]##*-r}; ISOREL=${ISOREL%%.*}
            [ $ISOREL -ge $MINREL ] && OGLIVE[i-1]="(*) ${OGLIVE[i-1]}"
        done
        select opt in "${OGLIVE[@]}"; do
            [ -n "$opt" ] && OGLIVEFILE=${opt##* } && break
        done
    fi
    # Get download size.
    SOURCELENGTH=$(curl -k --head --retry 5 --retry-delay 5 --max-time 30 $DOWNLOADURL/$OGLIVEFILE | awk -F: '/Content-Length:/ {print $2}')
    [ -n "$SOURCELENGTH" ] || raiseError download "$OGLIVEFILE"
    # Download ogLive.
    TARGETFILE=$DOWNLOADDIR/$OGLIVEFILE
    trap "rm -f $TARGETFILE" 1 2 3 6 9 15
    curl -k --retry 5 --retry-delay 5 --max-time 30 $DOWNLOADURL/$OGLIVEFILE -o $TARGETFILE || raiseError download "\"$OGLIVEFILE\"."
}

update_json() {
    local key=$1
    local value=$2
    result_json=$(jq --arg key "$key" --arg value "$value" '.[$key] = $value' <<< "$result_json")
}

add_message() {
    local message=$1
    result_json=$(jq --arg message "$message" '.messages += [$message]' <<< "$result_json")
}

# Install an ogLive client from a previously downloaded ISO image.
function install() {
    local OGLIVEFILE OGLIVEDIST OGLIVEREV OGLIVEKRNL OGLIVEDIR OGINITRD OGSQFS OGCLIENT=ogclient
    local COMPRESS SAMBAPASS TMPDIR RSYNCSERV RSYNCCLNT JSON_OUTPUT
    [ $# -ne 1 ] && { echo "{\"error\": \"USAGE_ERROR\", \"message\": \"Usage: install {iso_file}\"}"; exit 400; }

    OGLIVEFILE=$(realpath "$DOWNLOADDIR/$1")
    [ $(echo "$OGLIVEFILE" | wc -w) -gt 1 ] && { echo "{\"error\": \"USAGE_ERROR\", \"message\": \"Invalid ISO file: $1\"}"; exit 400; }
    [ ! -f "$OGLIVEFILE" ] && { echo "{\"error\": \"NOT_FOUND\", \"message\": \"ISO file $1 not found.\"}"; exit 404; }
    [ ! -r "$OGLIVEFILE" ] && { echo "{\"error\": \"ACCESS_DENIED\", \"message\": \"Cannot read ISO file $1.\"}"; exit 403; }
    [ ! -w "$(dirname "$INFOFILE")" ] && { echo "{\"error\": \"ACCESS_DENIED\", \"message\": \"Cannot write to configuration directory.\"}"; exit 403; }
    [ ! -w "$TFTPDIR" ] && { echo "{\"error\": \"ACCESS_DENIED\", \"message\": \"Cannot write to installation directory.\"}"; exit 403; }
    [ -z "$(file -b "$OGLIVEFILE" | grep "ISO.*ogClient")" ] && { echo "{\"error\": \"INVALID_FILE\", \"message\": \"File is not a valid ogLive ISO image.\"}"; exit 400; }

    OGLIVEDIST="$(echo "$OGLIVEFILE" | cut -f2 -d-)"
    OGLIVEREV="${OGLIVEFILE##*-}"; OGLIVEREV="${OGLIVEREV%%.*}"
    OGLIVEKRNL="$(echo "$OGLIVEFILE" | cut -f3- -d-)"; OGLIVEKRNL="${OGLIVEKRNL%-$OGLIVEREV.*}"
    OGLIVEARCH="$(echo "$OGLIVEFILE" | awk -F- '{print $(NF-1)}')"
    case "$OGLIVEARCH" in
        i386|amd64)
            OGLIVEKRNL="${OGLIVEKRNL%-$OGLIVEARCH}" ;;
        *)
            OGLIVEARCH="i386" ;;
    esac
    #OGLIVEDIR="$TFTPDIR/$DEFOGLIVE-${OGLIVEKRNL%%-*}-$OGLIVEARCH-$OGLIVEREV"
    #OGLIVEDIR="${OGLIVEDIR/amd64-/}"
    OGLIVEDIR=$TFTPDIR$(basename "$OGLIVEFILE" .iso)
    OGINITRD="$OGLIVEDIR/oginitrd.img"

    [ ! -r "$OGINITRD" ] && OGINITRD="$TFTPDIR/$DEFOGLIVE/oginitrd.img"
    if [ -r "$OGINITRD" ]; then
        COMPRESS=$(file -b "$OGINITRD" | awk '{print tolower($1);}')
        if [[ z$COMPRESS == zascii ]]; then
            COMPRESS=cat
        else
            COMPRESS="$COMPRESS -dc"
        fi
        SAMBAPASS=$($COMPRESS "$OGINITRD" | \
            cpio -i --to-stdout scripts/ogfunctions 2>/dev/null | \
            sed -n '/^[ \t].*OPTIONS=/s/.*pass=\(\w*\).*/\1/p')
    fi
    rm -fr "${OGLIVEDIR}.old" 2>/dev/null
    mv -f "$OGLIVEDIR" "${OGLIVEDIR}.old" 2>/dev/null

    TMPDIR=$OPENGNSYS/mnt
    TMPDIR_SQUASHFS=/tmp/ogclient_mount
    mv -f "$OGLIVEFILE" "$DOWNLOADDIR/oglive.iso" 2>/dev/null || { echo "{\"error\": \"RENAME_FAILED\", \"message\": \"Cannot rename $OGLIVEFILE to oglive.iso.\"}"; exit 500; }
    OGLIVEFILE="$DOWNLOADDIR/oglive.iso"

    mkdir -p "$OGLIVEDIR" "$TMPDIR" "$TMPDIR_SQUASHFS"
    [ ! -d "$OGLIVEDIR" ] && { echo "{\"error\": \"DIR_CREATION_FAILED\", \"message\": \"Failed to create/access directory $OGLIVEDIR.\"}"; exit 500; }
    [ ! -d "$TMPDIR" ] && { echo "{\"error\": \"DIR_CREATION_FAILED\", \"message\": \"Failed to create/access directory $TMPDIR.\"}"; exit 500; }
    [ ! -d "$TMPDIR_SQUASHFS" ] && { echo "{\"error\": \"DIR_CREATION_FAILED\", \"message\": \"Failed to create/access directory $TMPDIR_SQUASHFS.\"}"; exit 500; }

    trap "umount $TMPDIR" 1 2 3 6 9 15
    mount "$OGLIVEFILE" >/dev/null 2>&1 || { echo "{\"error\": \"MOUNT_FAILED\", \"message\": \"Failed to mount ISO file.\"}"; exit 500; }

    cp -va "$TMPDIR/ogclient/"* "$OGLIVEDIR" >/dev/null 2>&1 || { echo "{\"error\": \"COPY_FAILED\", \"message\": \"Cannot copy files to $OGLIVEDIR.\"}"; exit 500; }
    chmod -R u+w "$OGLIVEDIR" || { echo "{\"error\": \"PERMISSION_CHANGE_FAILED\", \"message\": \"Failed to change permissions for $OGLIVEDIR.\"}"; exit 500; }
    umount "$TMPDIR" >/dev/null 2>&1

    if [ ! -f "$INFOFILE" ]; then
        rm -f "$TFTPDIR/$DEFOGLIVE" "$TFTPDIR/$OGCLIENT"
        ln -vfs "$(basename "$OGLIVEDIR")" "$TFTPDIR/$DEFOGLIVE" >/dev/null 2>&1 || { echo "{\"error\": \"LINK_FAILED\", \"message\": \"Linking to $TFTPDIR/$DEFOGLIVE failed.\"}"; exit 500; }
        ln -vfs "$DEFOGLIVE" "$TFTPDIR/$OGCLIENT" >/dev/null 2>&1 || { echo "{\"error\": \"LINK_FAILED\", \"message\": \"Linking to $TFTPDIR/$OGCLIENT failed.\"}"; exit 500; }
    fi

    if [ -n "$SAMBAPASS" ]; then
        echo -ne "$SAMBAPASS\n$SAMBAPASS\n" | $OPENGNSYS/bin/setsmbpass "$(basename "$OGLIVEDIR")" >/dev/null 2>&1 || { echo "{\"error\": \"SET_SMBPASS_FAILED\", \"message\": \"Failed to set Samba password with SAMBAPASS.\"}"; exit 500; }
#    else
#        $OPENGNSYS/bin/setsmbpass "$(basename "$OGLIVEDIR")" >/dev/null 2>&1 || { echo "{\"error\": \"SET_SMBPASS_FAILED\", \"message\": \"Failed to set Samba password without SAMBAPASS.\"}"; exit 500; }
    fi

    find -L "$OGLIVEDIR" -type d -exec chmod 755 {} \; >/dev/null 2>&1 || { echo "{\"error\": \"CHMOD_FAILED\", \"message\": \"Failed to change permissions for directories in $OGLIVEDIR.\"}"; exit 500; }
    find -L "$OGLIVEDIR" -type f -exec chmod 644 {} \; >/dev/null 2>&1 || { echo "{\"error\": \"CHMOD_FAILED\", \"message\": \"Failed to change permissions for files in $OGLIVEDIR.\"}"; exit 500; }


    OGSQFS="$OGLIVEDIR/ogclient.sqfs"
    trap "exit 0" SIGINT
    TMPDIR=/tmp/oglive
    if mountpoint -q "$TMPDIR"; then
        umount "$TMPDIR" >/dev/null 2>&1 || { echo "{\"error\": \"UMOUNT_FAILED\", \"message\": \"Failed to unmount $TMPDIR.\"}"; exit 500; }
    fi

    mount "$TMPDIR_SQUASHFS" || { echo "{\"error\": \"MOUNT_FAILED\", \"message\": \"Failed to mount $TMPDIR_SQUASHFS.\"}"; exit 500; }
    RSYNCSERV=$(rsync --version 2>/dev/null | awk '/protocol/ {print $6}')
    RSYNCCLNT=$($TMPDIR_SQUASHFS/usr/bin/rsync --version 2>/dev/null | awk '/protocol/ {print $6}')
    if [ -z "$RSYNCSERV" ] || [ "$RSYNCSERV" -gt "${RSYNCCLNT:-1}" ]; then
        [ -e "$OPENGNSYS/client/bin/rsync-$RSYNCSERV" ] && mv -f "$OPENGNSYS/client/bin/rsync-$RSYNCSERV" "$OPENGNSYS/client/bin/rsync" 2>/dev/null
    else
        [ -e "$OPENGNSYS/client/bin/rsync" ] && mv -f "$OPENGNSYS/client/bin/rsync" "$OPENGNSYS/client/bin/rsync-$($OPENGNSYS/client/bin/rsync --version 2>/dev/null | awk '/protocol/ {print $6}')"
    fi
    umount "$TMPDIR_SQUASHFS" || sudo rm -rf "$TMPDIR_SQUASHFS"

    CHECKSUM_FILE="$OGLIVEDIR/ogclient.sqfs.sum"
    [ -f "$CHECKSUM_FILE" ] && OGLIVEID=$(cat "$CHECKSUM_FILE") || OGLIVEID="N/A"

    jq -n \
    --arg OGLIVEDIST "$OGLIVEDIST" \
    --arg OGLIVEKRNL "$OGLIVEKRNL" \
    --arg OGLIVEARCH "$OGLIVEARCH" \
    --arg OGLIVEREV "$OGLIVEREV" \
    --arg OGLIVEDIR "$OGLIVEDIR" \
    '{
        OGLIVEDIST: $OGLIVEDIST,
        OGLIVEKRNL: $OGLIVEKRNL,
        OGLIVEARCH: $OGLIVEARCH,
        OGLIVEREV: $OGLIVEREV,
        OGLIVEDIR: $OGLIVEDIR
    }' > "$OGLIVEDIR/oglive_info.json"

    [ ! -f "$OGLIVEDIR/oglive_info.json" ] && { echo "{\"error\": \"JSON_CREATION_FAILED\", \"message\": \"Failed to create oglive_info.json.\"}"; exit 500; }
    sudo chown -R :opengnsys "$OGLIVEDIR" >/dev/null 2>&1 || { echo "{\"error\": \"CHOWN_FAILED\", \"message\": \"Failed to change ownership for $OGLIVEDIR.\"}"; exit 500; }
    JSON_OUTPUT=$(jq -n \
        --arg id "$OGLIVEID" \
        --arg dist "$OGLIVEDIST" \
        --arg krnl "$OGLIVEKRNL" \
        --arg arch "$OGLIVEARCH" \
        --arg rev "$OGLIVEREV" \
        --arg dir "$OGLIVEDIR" \
        '{status: "success", message: {id: $id, distribution: $dist, kernel: $krnl, architecture: $arch, revision: $rev, directory: $dir}}')

    echo "$JSON_OUTPUT"
    exit 0
}


# Uninstall an ogLive client.
function uninstall() {
    local CHECKSUM DIR DEFAULT_OGLIVE_DIR DEFAULT_CHECKSUM

    # Validar que se proporcionó exactamente un argumento (el checksum)
    [ $# -ne 1 ] && { echo "{\"error\": \"BAD_REQUEST\", \"message\": \"usage: uninstall {checksum}\"}"; exit 400; }

    CHECKSUM=$1

    # Verificar acceso a los directorios necesarios
    [ ! -w $TFTPDIR ] && { echo "{\"error\": \"SERVER_ERROR\", \"message\": \"access installation directory.\"}"; exit 500; }

    # Verificar el enlace simbólico del ogLive por defecto
    if [ -L "$TFTPDIR/ogLive" ]; then
        DEFAULT_OGLIVE_DIR=$(readlink -f "$TFTPDIR/ogLive")
        DEFAULT_CHECKSUM_FILE="$DEFAULT_OGLIVE_DIR/ogclient.sqfs.sum"

        # Verificar si el archivo de checksum del ogLive por defecto existe
        if [ -f "$DEFAULT_CHECKSUM_FILE" ]; then
            DEFAULT_CHECKSUM=$(cat "$DEFAULT_CHECKSUM_FILE" | cut -d ' ' -f 1)

            # Comparar el checksum proporcionado con el del ogLive por defecto
            if [ "$CHECKSUM" == "$DEFAULT_CHECKSUM" ]; then
                echo "{\"error\": \"FORBIDDEN\", \"message\": \"Cannot uninstall the default ogLive client.\"}"
                exit 403
            fi
        fi
    fi

    # Buscar el directorio correspondiente al checksum
    DIR=$(find $TFTPDIR -type f -name 'ogclient.sqfs.sum' -exec grep -l "$CHECKSUM" {} \; | xargs -I{} dirname {})

    # Si no se encuentra el directorio, devolver error 404
    if [ -z "$DIR" ]; then
        echo "{\"error\": \"NOT_FOUND\", \"message\": \"ogLive client with checksum $CHECKSUM not found.\"}"
        exit 404
    fi

    # Eliminar archivos y directorio, redirigiendo la salida a /dev/null
    rm -vfr $DIR > /dev/null 2>&1

    # Comprobar si la eliminación tuvo éxito, si no, devolver error 500
    if [ -d "$DIR" ]; then
        echo "{\"error\": \"SERVER_ERROR\", \"message\": \"Failed to uninstall ogLive client in $DIR.\"}"
        exit 500
    fi

    # Devolver mensaje de éxito
    echo "{\"message\": \"ogLive client uninstalled successfully.\", \"details\": \"Removed directory: $DIR\"}"
    exit 200
}


# Get information about the default ogLive client.
function get_default() {
    local DEFAULT_LINK="$TFTPDIR/$DEFOGLIVE"
    local DIR OGLIVEJSON OGLIVEDIST OGLIVEKRNL OGLIVEARCH OGLIVEREV OGLIVEDIR CHECKSUM_FILE CHECKSUM

    # Verificar que el enlace simbólico del ogLive por defecto existe.
    if [ ! -L "$DEFAULT_LINK" ]; then
        echo "{\"error\": \"Default ogLive client link not found.\"}"
        exit 1
    fi

    # Obtener el directorio real al que apunta el enlace simbólico.
    DIR=$(readlink -f "$DEFAULT_LINK")

    # Si no se encuentra el directorio, devolver error.
    if [ -z "$DIR" ]; then
        echo "{\"error\": \"Default ogLive client directory not found.\"}"
        exit 1
    fi

    # Verificar que el archivo oglive_info.json existe en el directorio.
    OGLIVEJSON="$DIR/oglive_info.json"
    if [ ! -f "$OGLIVEJSON" ]; then
        echo "{\"error\": \"oglive_info.json not found in $DIR.\"}"
        exit 1
    fi

    # Leer los datos del archivo JSON.
    OGLIVEDIST=$(jq -r '.OGLIVEDIST' "$OGLIVEJSON")
    OGLIVEKRNL=$(jq -r '.OGLIVEKRNL' "$OGLIVEJSON")
    OGLIVEARCH=$(jq -r '.OGLIVEARCH' "$OGLIVEJSON")
    OGLIVEREV=$(jq -r '.OGLIVEREV' "$OGLIVEJSON")
    OGLIVEDIR=$(jq -r '.OGLIVEDIR' "$OGLIVEJSON")

    # Obtener el checksum del archivo ogclient.sqfs.sum
    CHECKSUM_FILE="$DIR/ogclient.sqfs.sum"
    if [ -f "$CHECKSUM_FILE" ]; then
        CHECKSUM=$(cat "$CHECKSUM_FILE" | cut -d ' ' -f 1)
    else
        CHECKSUM=""
    fi

    # Construir el JSON con la información.
    local INFO=$(cat << EOT
{
  "id": "$CHECKSUM",
  "distribution": "$OGLIVEDIST",
  "kernel": "$OGLIVEKRNL",
  "architecture": "$OGLIVEARCH",
  "revision": "$OGLIVEREV",
  "directory": "$OGLIVEDIR"
}
EOT
    )

    # Devolver la información en formato JSON.
    echo "$INFO"
}


# Set default ogLive client by checksum.
function set_default() {
    local CHECKSUM=$1
    local DIR

    # Validar que se proporcionó exactamente un argumento (el checksum)
    [ $# -ne 1 ] && { echo "{\"error\": \"usage: set-default {checksum}\"}"; exit 1; }

    # Verificar acceso a los directorios necesarios
    [ ! -w $TFTPDIR ] && { echo "{\"error\": \"access installation directory.\"}"; exit 1; }

    # Buscar el directorio correspondiente al checksum
    DIR=$(find $TFTPDIR -type f -name 'ogclient.sqfs.sum' -exec grep -l "$CHECKSUM" {} \; | xargs -I{} dirname {} | grep -v ".old")

    # Si no se encuentra el directorio, devolver error
    if [ -z "$DIR" ]; then
        echo "{\"error\": \"ogLive client with checksum $CHECKSUM not found.\"}"
        exit 1
    fi

    # Eliminar el enlace simbólico existente y crear uno nuevo
    rm -f $TFTPDIR/$DEFOGLIVE > /dev/null 2>&1
    ln -vfs $(basename $DIR) $TFTPDIR/$DEFOGLIVE > /dev/null 2>&1

    # Comprobar si el enlace simbólico se creó correctamente
    if [ "$(readlink -f $TFTPDIR/$DEFOGLIVE)" == "$(readlink -f $DIR)" ]; then
        echo "{\"message\": \"ogLive client set as default successfully.\", \"details\": \"$DIR\"}"
    else
        echo "{\"error\": \"Failed to set default ogLive client.\"}"
        exit 1
    fi
}

# Get disk usage information
function disk_usage() {
    DISK_INFO=$(df / | awk 'NR==2{print "{\"total\":\""$2"\", \"used\":\""$3"\", \"available\":\""$4"\", \"percentage\":\""$5"\"}"}')
    echo $DISK_INFO
}

# Function to list installed ogLive clients and the default ogLive client
function list_installed_oglives() {
    local INST NF DEF

    # Verificar si el directorio TFTPDIR es accesible
    if [ ! -d "$TFTPDIR" ]; then
        echo '{"error": "SERVER_ERROR", "message": "TFTP directory not found or not accessible."}'
        exit 500
    fi

    # Buscar directorios de ogLive instalados
    INST=$(find "$TFTPDIR/" -type d -name "$DEFOGLIVE-*" -a ! -name "*.old" -printf "%f\n" 2>/dev/null | sort)
    if [ -z "$INST" ]; then
        echo '{"error": "NOT_FOUND", "message": "No installed ogLive clients found."}'
        exit 404
    fi

    local installed_ogLives=()
    local oglive_count=0

    for i in $INST; do
        local OGLIVEDIR="$TFTPDIR/$i"
        local OGLIVEDIST=""
        local OGLIVEKRNL=""
        local OGLIVEARCH=""
        local OGLIVEREV=""
        local CHECKSUM=""
        local OGLIVEJSON="$OGLIVEDIR/oglive_info.json"
        local CHECKSUM_FILE="$OGLIVEDIR/ogclient.sqfs.sum"

        # Intentar obtener el ID desde el archivo ogclient.sqfs.sum
        if [ -f "$CHECKSUM_FILE" ]; then
            CHECKSUM=$(cut -d ' ' -f 1 "$CHECKSUM_FILE")
        else
            local DATA=$(jq -n \
                --arg id "unknown" \
                --arg error "CHECKSUM_NOT_FOUND" \
                --arg message "Checksum file not found or invalid in $OGLIVEDIR. Manual deletion is recommended." \
                '{id: $id, error: $error, message: $message}')
            installed_ogLives+=("$DATA")
            continue
        fi

        # Verificar que el archivo oglive_info.json existe en el directorio
        if [ -f "$OGLIVEJSON" ]; then
            OGLIVEDIST=$(jq -r '.OGLIVEDIST' "$OGLIVEJSON")
            OGLIVEKRNL=$(jq -r '.OGLIVEKRNL' "$OGLIVEJSON")
            OGLIVEARCH=$(jq -r '.OGLIVEARCH' "$OGLIVEJSON")
            OGLIVEREV=$(jq -r '.OGLIVEREV' "$OGLIVEJSON")

            # Crear el JSON con los datos del ogLive
            local DATA=$(jq -n \
                --arg id "$CHECKSUM" \
                --arg dist "$OGLIVEDIST" \
                --arg krnl "$OGLIVEKRNL" \
                --arg arch "$OGLIVEARCH" \
                --arg rev "$OGLIVEREV" \
                --arg dir "$OGLIVEDIR" \
                '{id: $id, distribution: $dist, kernel: $krnl, architecture: $arch, revision: $rev, directory: $dir}')
            installed_ogLives+=("$DATA")
        else
            # Informar que el archivo oglive_info.json no existe y que el ogLive puede estar corrupto
            local DATA=$(jq -n \
                --arg id "$CHECKSUM" \
                --arg error "CORRUPT_OG_LIVE" \
                --arg message "oglive_info.json not found in $OGLIVEDIR. ogLive may be corrupted." \
                '{id: $id, error: $error, message: $message}')
            installed_ogLives+=("$DATA")
            continue
        fi

        oglive_count=$((oglive_count + 1))

        # Verificar si es el ogLive por defecto
        [ -n "$(stat -c "%N" "$TFTPDIR/$DEFOGLIVE" | awk '$3~/'$i'/ {print}')" ] && DEF="$i"
    done

    # Verificar si se encontraron ogLives
    if [ "$oglive_count" -eq 0 ]; then
        echo '{"error": "NOT_FOUND", "message": "No valid ogLive clients found."}'
        exit 404
    fi

    local default_oglive=$(basename "$(readlink -f "$TFTPDIR/$DEFOGLIVE")")

    # Crear el JSON final con la lista de ogLives instalados y el ogLive por defecto
    jq -n \
        --arg default_oglive "$default_oglive" \
        --argjson installed_ogLives "$(printf '%s\n' "${installed_ogLives[@]}" | jq -s .)" \
        '{
            default_oglive: $default_oglive,
            installed_ogLives: $installed_ogLives
        }'
    exit 0
}

# Get information about an installed ogLive client.
function get_info() {
    local CHECKSUM="$1"
    local DIR OGLIVEDIST OGLIVEKRNL OGLIVEARCH OGLIVEREV OGLIVEDIR OGLIVEJSON

    # Verificar que se proporcionó un checksum.
    [ -z "$CHECKSUM" ] && { echo "{\"error\": \"usage: get_info {checksum}\"}"; exit 1; }

    # Verificar acceso al directorio de instalación.
    [ ! -w $TFTPDIR ] && { echo "{\"error\": \"access installation directory.\"}"; exit 1; }

    # Buscar el directorio correspondiente al checksum, excluyendo los que terminan en .old.
    DIR=$(find $TFTPDIR -type f -name 'ogclient.sqfs.sum' -exec grep -l "$CHECKSUM" {} \; | grep -v '.old' | xargs -I{} dirname {})

    # Si no se encuentra el directorio, devolver error.
    if [ -z "$DIR" ]; then
        echo "{\"error\": \"ogLive client with checksum $CHECKSUM not found.\"}"
        exit 1
    fi

    # Verificar que el archivo oglive_info.json existe en el directorio.
    OGLIVEJSON="$DIR/oglive_info.json"
    if [ ! -f "$OGLIVEJSON" ]; then
        echo "{\"error\": \"oglive_info.json not found in $DIR.\"}"
        exit 1
    fi

    # Leer los datos del archivo JSON.
    OGLIVEDIST=$(jq -r '.OGLIVEDIST' "$OGLIVEJSON")
    OGLIVEKRNL=$(jq -r '.OGLIVEKRNL' "$OGLIVEJSON")
    OGLIVEARCH=$(jq -r '.OGLIVEARCH' "$OGLIVEJSON")
    OGLIVEREV=$(jq -r '.OGLIVEREV' "$OGLIVEJSON")
    OGLIVEDIR=$(jq -r '.OGLIVEDIR' "$OGLIVEJSON")

    # Construir el JSON con los datos del ogLive y el checksum (id).
    local INFO=$(cat << EOT
{
  "id": "$CHECKSUM",
  "distribution": "$OGLIVEDIST",
  "kernel": "$OGLIVEKRNL",
  "architecture": "$OGLIVEARCH",
  "revision": "$OGLIVEREV",
  "directory": "$OGLIVEDIR"
}
EOT
    )

    # Devolver la información en formato JSON.
    echo "$INFO"
}

# Function to check the status of services
function check_services_status() {
    local SERVICES=("tftpd-hpa.service" "nginx.service")
    declare -A STATUS_MAP

    for service in "${SERVICES[@]}"; do
        if systemctl list-units --type=service --all | grep -q "$service"; then
            STATUS=$(systemctl is-active "$service")
            STATUS_MAP[$service]=$STATUS
        else
            STATUS_MAP[$service]="not installed"
        fi
    done

    local json_output=$(jq -n \
        --arg tftpboot "${STATUS_MAP['tftpd-hpa.service']}" \
        --arg nginx "${STATUS_MAP['nginx.service']}" \
        '{
            tftpboot: $tftpboot,
            nginx: $nginx
        }')
    
    echo "$json_output"
}
# Main program.

# Access control.
if [ "$USER" = "root" ] || [ "$USER" = "opengnsys" ] || groups $USER | grep -qw "opengnsys"; then
    ACCESS="root"
else
    raiseError access "Need to be root, opengnsys or a member of the opengnsys group."
fi

# Check dependencies.
which sponge &>/dev/null || raiseError notfound "Need to install \"moreutils\"."

# Commands control.
shopt -s extglob
CMDS='+(help|disk_usage|list_installed_oglives|check_services_status|download|install|uninstall|get_default|set_default|get_info)'

case "$1" in
    $CMDS)  COMMAND="${1/-/}"; shift; $COMMAND "$@" ;;
    *)      raiseError usage ;;
esac

exit $?


