diff --git a/.env b/.env new file mode 100644 index 0000000..304c789 --- /dev/null +++ b/.env @@ -0,0 +1,40 @@ +# In all environments, the following files are loaded if they exist, +# the latter taking precedence over the former: +# +# * .env contains default values for the environment variables needed by the app +# * .env.local uncommitted file with local overrides +# * .env.$APP_ENV committed environment-specific defaults +# * .env.$APP_ENV.local uncommitted environment-specific overrides +# +# Real environment variables win over .env files. +# +# DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. +# https://symfony.com/doc/current/configuration/secrets.html +# +# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). +# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration + +###> symfony/framework-bundle ### +APP_ENV=dev +APP_SECRET=d423d1302b974417d415b10bcde25767 +###< symfony/framework-bundle ### + +###> doctrine/doctrine-bundle ### +# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url +# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml +# +# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" +# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8&charset=utf8mb4" +DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=15&charset=utf8" +###< doctrine/doctrine-bundle ### + +###> symfony/messenger ### +# Choose one of the transports below +# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages +# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages +MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 +###< symfony/messenger ### + +###> symfony/mailer ### +# MAILER_DSN=null://null +###< symfony/mailer ### diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de562d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ + +###> symfony/framework-bundle ### +/.env.local +/.env.local.php +/.env.*.local +/config/secrets/prod/prod.decrypt.private.php +/public/bundles/ +/var/ +/vendor/ +###< symfony/framework-bundle ### + +###> phpunit/phpunit ### +/phpunit.xml +.phpunit.result.cache +###< phpunit/phpunit ### + +###> symfony/phpunit-bridge ### +.phpunit.result.cache +/phpunit.xml +###< symfony/phpunit-bridge ### diff --git a/bin/composer.phar b/bin/composer.phar new file mode 100755 index 0000000..bd5bab8 Binary files /dev/null and b/bin/composer.phar differ diff --git a/bin/console b/bin/console new file mode 100755 index 0000000..c933dc5 --- /dev/null +++ b/bin/console @@ -0,0 +1,17 @@ +#!/usr/bin/env php +/dev/null) OGLIVEISO=$(basename $6 2>/dev/null) + # JSON data for installed ogLive. + DATA=$(cat << EOT | jq . +{"distribution":"$OGLIVEDIST","kernel":"$OGLIVEKRNL","architecture":"$OGLIVEARCH","revision":"$OGLIVEREV","directory":"$OGLIVEDIR","iso":"$OGLIVEISO"} +EOT + ) + # Check JSON file consistency. + if [ "$(jq -c keys $INFOFILE 2>/dev/null)" == '["default","oglive"]' ]; then + # Check if ogLive is defined into JSON file. + n=$(jq ".oglive | length" $INFOFILE) + for ((i=0; i/dev/null || raiseError access "Installation directory." + [ ! -f $OGCLIENT/ogvmlinuz ] && raiseError notfound "\"ogclient\"." + # Add entry to JSON file using ogclient kernel version. + OGLIVEKRNL=$(file -bkr $OGCLIENT/ogvmlinuz | awk '/Linux/ {for(i=1;i<=NF;i++) if($i~/version/) {v=$(i+1);sub(/-.*/,"",v);print v}}') + OGLIVEDIR=$DEFOGLIVE-$OGLIVEKRNL + [ -r $OLDINFOFILE ] && OGLIVEISO="$(head -1 $OLDINFOFILE)" + addToJson "$(echo $OGLIVEISO|cut -f2 -d-)" "$OGLIVEKRNL" "i386" "${OGLIVEISO##*-}" "$OGLIVEDIR" "$OGLIVEISO.iso" + # Rename directory, link to default and clean old files. + mv -v $OGCLIENT $OGLIVEDIR + ln -vfs $OGLIVEDIR $DEFOGLIVE + rm -f $OGCLIENT + ln -vfs $DEFOGLIVE $OGCLIENT + mv -v $OGCLIENT.old $OGLIVEDIR.old 2>/dev/null + rm -fv {ogvmlinuz,oginitrd.img}{,.sum} $OLDINFOFILE + popd >/dev/null + # Delete old config file. + rm -f $OLDINFOFILE +} + +# Show script configuration parameters. +function config() { + local DATA + DATA=$(cat << EOT +[ + { "param": "config-file", "description": "Configuration file", "value": "$INFOFILE" }, + { "param": "download-url", "description": "ogLive download URL", "value": "$DOWNLOADURL" }, + { "param": "download-dir", "description": "ogLive download directory", "value": "$DOWNLOADDIR" }, + { "param": "install-dir", "description": "ogLive installation directory", "value": "$TFTPDIR" }, + { "param": "default-name", "description": "Default ogLive name", "value": "$DEFOGLIVE" }, + { "param": "min-release", "description": "Minimum compatibility release", "value": "r$MINREL" } +] +EOT + ) + case $# in + 0) # Show all parameters. + echo "$DATA" | jq -r '.[] | .description + " (" + .param + ")," + .value' | column -ts, + ;; + 1) # Show specified parameter. + DATA=$(echo "$DATA" | jq -r ".[] | select(.param==\"$1\").value") + [ "$DATA" ] || raiseError notfound "\"$1\"." + echo "$DATA" + ;; + *) # Usage error. + raiseError usage + ;; + esac +} + +# 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 OGLIVE NISOS i HTTPCODE ISOREL + + # 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 selected_name="$1" + + # Obtener la lista de archivos disponibles. + OGLIVE=( $(curl -k --silent $DOWNLOADURL | grep "$DEFOGLIVE.*iso") ) + + # Buscar el archivo seleccionado por nombre. + OGLIVEFILE="" + for iso in "${OGLIVE[@]}"; do + if [[ "$iso" == *"$selected_name"* ]]; then + OGLIVEFILE=$iso + break + fi + done + + [ -n "$OGLIVEFILE" ] || raiseError download "Nombre \"$selected_name\" inválido." + + # Obtener el tamaño de descarga. + local 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" + + # Descargar ogLive. + local TARGETFILE="$DOWNLOADDIR/$OGLIVEFILE" + trap "rm -f $TARGETFILE" 1 2 3 6 9 15 + curl -k --retry 5 --retry-delay 5 "$DOWNLOADURL/$OGLIVEFILE" -o "$TARGETFILE" || raiseError download "\"$OGLIVEFILE\"." + install "$OGLIVEFILE" + 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 + OGLIVE=( $(curl -k --silent $DOWNLOADURL | grep "$DEFOGLIVE.*iso") ) + NISOS=${#OGLIVE[@]} + + local downloads=() + + for i in $(seq 1 $NISOS); do + local installed=false + local compatible=false + + [ -e $DOWNLOADDIR/${OGLIVE[i-1]} ] && installed=true + ISOREL=${OGLIVE[i-1]##*-r}; ISOREL=${ISOREL%%.*} + [ $ISOREL -ge $MINREL ] && compatible=true + + local DATA=$(jq -n \ + --arg id "$i" \ + --arg filename "${OGLIVE[i-1]}" \ + --argjson installed "$installed" \ + --argjson compatible "$compatible" \ + '{id: $id, filename: $filename, 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 "{\"status\": \"error\", \"error\": \"usage\"}"; exit 1; } + + OGLIVEFILE=$(realpath $DOWNLOADDIR/$1) + [ $(echo $OGLIVEFILE | wc -w) -gt 1 ] && { echo "{\"status\": \"error\", \"error\": \"usage\"}"; exit 1; } + [ ! -f $OGLIVEFILE ] && { echo "{\"status\": \"error\", \"error\": \"not found $1.\"}"; exit 1; } + [ ! -r $OGLIVEFILE ] && { echo "{\"status\": \"error\", \"error\": \"access $1.\"}"; exit 1; } + [ ! -w $(dirname $INFOFILE) ] && { echo "{\"status\": \"error\", \"error\": \"access configuration directory.\"}"; exit 1; } + [ ! -w $TFTPDIR ] && { echo "{\"status\": \"error\", \"error\": \"access installation directory.\"}"; exit 1; } + [ -z "$(file -b $OGLIVEFILE | grep "ISO.*ogClient")" ] && { echo "{\"status\": \"error\", \"error\": \"File is not an ogLive ISO image.\"}"; exit 1; } + + 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-/}" + + OGINITRD=$OGLIVEDIR/oginitrd.img + [ ! -r $OGINITRD ] && OGINITRD=$TFTPDIR/$DEFOGLIVE/oginitrd.img + if [ -r $OGINITRD ]; then + COMPRESS=$(file -b "$OGINITRD" | awk '{print tolower($1);}') + SAMBAPASS=$($COMPRESS -dc $OGINITRD | \ + cpio -i --to-stdout scripts/ogfunctions 2>/dev/null | \ + sed -n '/^[ \t].*OPTIONS=/s/.*pass=\(\w*\).*/\1/p') + fi + + rm -fr ${OGLIVEDIR}.old + mv -f $OGLIVEDIR ${OGLIVEDIR}.old 2>/dev/null + + TMPDIR=/tmp/${OGLIVEFILE%.iso} + mkdir -p $OGLIVEDIR $TMPDIR + trap "umount $TMPDIR; rm -fr $TMPDIR" 1 2 3 6 9 15 + mount -o loop,ro $OGLIVEFILE $TMPDIR >/dev/null 2>&1 || { echo "{\"status\": \"error\", \"error\": \"mount failed.\"}"; exit 1; } + cp -va $TMPDIR/ogclient/* $OGLIVEDIR >/dev/null 2>&1 || { echo "{\"status\": \"error\", \"error\": \"Cannot copy files to ogLive directory.\"}"; exit 1; } + 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 "{\"status\": \"error\", \"error\": \"Linking to $TFTPDIR/$DEFOGLIVE failed.\"}"; exit 1; } + ln -vfs $DEFOGLIVE $TFTPDIR/$OGCLIENT >/dev/null 2>&1 || { echo "{\"status\": \"error\", \"error\": \"Linking to $TFTPDIR/$OGCLIENT failed.\"}"; exit 1; } + fi + + if [ -n "$SAMBAPASS" ]; then + echo -ne "$SAMBAPASS\n$SAMBAPASS\n" | $OPENGNSYS/bin/setsmbpass "$(basename $OGLIVEDIR)" >/dev/null 2>&1 || { echo "{\"status\": \"error\", \"error\": \"setsmbpass failed.\"}"; exit 1; } + else + $OPENGNSYS/bin/setsmbpass "$(basename $OGLIVEDIR)" >/dev/null 2>&1 || { echo "{\"status\": \"error\", \"error\": \"setsmbpass failed.\"}"; exit 1; } + fi + + find -L $OGLIVEDIR -type d -exec chmod 755 {} \; >/dev/null 2>&1 || { echo "{\"status\": \"error\", \"error\": \"chmod directories failed.\"}"; exit 1; } + find -L $OGLIVEDIR -type f -exec chmod 644 {} \; >/dev/null 2>&1 || { echo "{\"status\": \"error\", \"error\": \"chmod files failed.\"}"; exit 1; } + chown -R :opengnsys $OGLIVEDIR >/dev/null 2>&1 || { echo "{\"status\": \"error\", \"error\": \"chown failed.\"}"; exit 1; } + + OGSQFS=$OGLIVEDIR/ogclient.sqfs + + if mount -o loop,ro $OGSQFS $TMPDIR >/dev/null 2>&1; then + RSYNCSERV=$(rsync --version 2>/dev/null | awk '/protocol/ {print $6}') + RSYNCCLNT=$(chroot $TMPDIR /usr/bin/rsync --version 2>/dev/null | awk '/protocol/ {print $6}') + + if [ -z "$RSYNCSERV" ] || [ "$RSYNCSERV" -gt "${RSYNCCLNT:-1}" ]; then + if [ -e "$OPENGNSYS/client/bin/rsync-$RSYNCSERV" ]; then + mv -f "$OPENGNSYS/client/bin/rsync-$RSYNCSERV" "$OPENGNSYS/client/bin/rsync" 2>/dev/null + fi + else + if [ -e "$OPENGNSYS/client/bin/rsync" ]; then + mv -f "$OPENGNSYS/client/bin/rsync" "$OPENGNSYS/client/bin/rsync-$($OPENGNSYS/client/bin/rsync --version 2>/dev/null | awk '/protocol/ {print $6}')" + fi + fi + + umount $TMPDIR >/dev/null 2>&1 + rmdir $TMPDIR >/dev/null 2>&1 || rm -rf $TMPDIR >/dev/null 2>&1 + fi + + # Crear JSON output + JSON_OUTPUT=$(jq -n \ + --arg dist "$OGLIVEDIST" \ + --arg krnl "$OGLIVEKRNL" \ + --arg arch "$OGLIVEARCH" \ + --arg rev "$OGLIVEREV" \ + --arg dir "$OGLIVEDIR" \ + --arg iso "$(basename "$OGLIVEFILE")" \ + '{status: "success", messages: [], result: {distribution: $dist, kernel: $krnl, architecture: $arch, revision: $rev, directory: $dir, iso: $iso}}') + + echo "$JSON_OUTPUT" +} + + + +# Uninstall an ogLive client. +function uninstall() { + local CHECKSUM DIR + + # Validar que se proporcionó exactamente un argumento (el checksum) + [ $# -ne 1 ] && { echo "{\"error\": \"usage: uninstall {checksum}\"}"; exit 1; } + + CHECKSUM=$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 {}) + + # 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 archivos y directorio, redirigiendo la salida a /dev/null + rm -vfr $DIR > /dev/null 2>&1 + + # Comprobar si la eliminación tuvo éxito + if [ -d "$DIR" ]; then + echo "{\"error\": \"Failed to uninstall ogLive client in $DIR.\"}" + exit 1 + fi + + # Devolver mensaje de éxito + echo "{\"message\": \"ogLive client uninstalled successfully.\", \"details\": \"Removed directory: $DIR\"}" +} + +# Get information about the default ogLive client. +function get_default() { + local DEFAULT_LINK="$TFTPDIR/$DEFOGLIVE" + local DIR OGLIVEDIST OGLIVEKRNL OGLIVEARCH OGLIVEREV OGLIVEISO OGLIVEDIR + + # 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 + + # Obtener la información del ogLive a partir del nombre del directorio. + OGLIVEDIR=$(basename "$DIR") + OGLIVEDIST="$(echo $OGLIVEDIR | cut -d- -f2)" + OGLIVEKRNL="$(echo $OGLIVEDIR | cut -d- -f3)" + OGLIVEARCH="amd64" # Suponiendo que siempre es amd64 + OGLIVEREV="$(echo $OGLIVEDIR | cut -d- -f4)" + OGLIVEISO="" # No tenemos la información del ISO aquí, podría necesitarse un ajuste si se requiere + + # Construir el JSON con la información. + local INFO=$(cat << EOT +{ + "distribution": "$OGLIVEDIST", + "kernel": "$OGLIVEKRNL", + "architecture": "$OGLIVEARCH", + "revision": "$OGLIVEREV", + "directory": "$DIR", + "iso": "$OGLIVEISO" +} +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 +} + +# Rebuild a lost configuration file. +function rebuild() { + local i INST NF DEF + [ $# -ne 0 ] && raiseError usage + [ -f $INFOFILE ] && raiseError access "Configuration file exists." + INST=$(find $TFTPDIR/ -type d -name "$DEFOGLIVE-*" -a ! -name "*.old" -printf "%f\n" | sort) + for i in $INST; do + NF=$(echo $i | awk -F- '{print NF-1}') + case $NF in + 1) addToJson "" "$(echo $i|cut -f2 -d-)" "i386" "" "$i" "" ;; + 2) eval addToJson $(echo $i | awk -F- '{printf "\"\" %s amd64 %s %s \"\"",$2,$3,$0}') ;; + 3) eval addToJson $(echo $i | awk -F- '{if ($3=="i386") printf "\"\" %s %s %s %s \"\"",$2,$3,$4,$0; else printf "%s %s i386 %s %s \"\"",$2,$3,$4,$0}') ;; + 4) eval addToJson $(echo $i | awk -F- '{printf "%s %s %s %s %s \"\"",$2,$3,$4,$5,$0}') ;; + esac + # Check for is default oglive. + [ -n "$(stat -c "%N" $TFTPDIR/$DEFOGLIVE | awk '$3~/'$i'/ {print}')" ] && DEF="$i" + done + # Set default ogLive. + [ -n "$DEF" ] && setdefault $(search $DEF) +} + +# Assign an ISO file to a JSON entry. +function assign() { +local ISOFILE DIR + [ $# -ne 2 ] && raiseError usage + [ ! -w $INFOFILE ] && raiseError access "Configuration file." + # Check if ISO file and index directory exist. + ISOFILE=$DOWNLOADFILE/$1 + [ ! -f $DOWNLOADDIR/$ISOFILE ] && raiseError notfound "ISO file \"$1\"." + DIR=$(search $2 2>/dev/null) + [ ! -d $TFTPDIR/$DIR ] && raiseError notfound "Directory for index \"$2\"." + # Assign ISO file to JSON entry. + jq ".oglive[$2].iso=\"$1\"" $INFOFILE | sponge $INFOFILE && jq ".oglive[$2]" $INFOFILE +} + +# Get disk usage information +function disk_usage() { + DISK_INFO=$(df -h / | 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 + INST=$(find $TFTPDIR/ -type d -name "$DEFOGLIVE-*" -a ! -name "*.old" -printf "%f\n" | sort) + local installed_ogLives=() + + for i in $INST; do + NF=$(echo $i | awk -F- '{print NF-1}') + local OGLIVEDIST="" + local OGLIVEKRNL="" + local OGLIVEARCH="" + local OGLIVEREV="" + local CHECKSUM="" + local CHECKSUM_FILE="$TFTPDIR/$i/ogclient.sqfs.sum" + + if [ -f "$CHECKSUM_FILE" ]; then + CHECKSUM=$(cat "$CHECKSUM_FILE" | cut -d ' ' -f 1) + fi + + case $NF in + 1) OGLIVEDIST="" OGLIVEKRNL=$(echo $i|cut -f2 -d-) OGLIVEARCH="i386" OGLIVEREV="" ;; + 2) eval $(echo $i | awk -F- '{printf "OGLIVEDIST=\"\" OGLIVEKRNL=%s OGLIVEARCH=amd64 OGLIVEREV=%s OGLIVEDIR=%s",$2,$3,$0}') ;; + 3) eval $(echo $i | awk -F- '{if ($3=="i386") printf "OGLIVEDIST=\"\" OGLIVEKRNL=%s OGLIVEARCH=%s OGLIVEREV=%s OGLIVEDIR=%s",$2,$3,$4,$0; else printf "OGLIVEDIST=%s OGLIVEKRNL=%s OGLIVEARCH=i386 OGLIVEREV=%s OGLIVEDIR=%s",$2,$3,$4,$0}') ;; + 4) eval $(echo $i | awk -F- '{printf "OGLIVEDIST=%s OGLIVEKRNL=%s OGLIVEARCH=%s OGLIVEREV=%s OGLIVEDIR=%s",$2,$3,$4,$5,$0}') ;; + esac + + local DATA=$(jq -n \ + --arg id "$CHECKSUM" \ + --arg dist "$OGLIVEDIST" \ + --arg krnl "$OGLIVEKRNL" \ + --arg arch "$OGLIVEARCH" \ + --arg rev "$OGLIVEREV" \ + --arg dir "$TFTPDIR/$OGLIVEDIR" \ + --arg iso "" \ + '{id: $id, distribution: $dist, kernel: $krnl, architecture: $arch, revision: $rev, directory: $dir, iso: $iso}') + + installed_ogLives+=("$DATA") + + [ -n "$(stat -c "%N" $TFTPDIR/$DEFOGLIVE | awk '$3~/'$i'/ {print}')" ] && DEF="$i" + done + + local default_oglive=$(basename $(readlink -f $TFTPDIR/$DEFOGLIVE)) + + 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 + }' +} + +# Get information about an installed ogLive client. +function get_info() { + local CHECKSUM="$1" + local DIR OGLIVEDIST OGLIVEKRNL OGLIVEARCH OGLIVEREV OGLIVEISO OGLIVEDIR + + # 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 + + # Obtener la información del ogLive a partir del nombre del directorio. + OGLIVEDIR=$(basename "$DIR") + OGLIVEDIST="$(echo $OGLIVEDIR | cut -d- -f2)" + OGLIVEKRNL="$(echo $OGLIVEDIR | cut -d- -f3)" + OGLIVEARCH="amd64" # Suponiendo que siempre es amd64 + OGLIVEREV="$(echo $OGLIVEDIR | cut -d- -f4)" + OGLIVEISO="" # No tenemos la información del ISO aquí, podría necesitarse un ajuste si se requiere + + # Construir el JSON con la información. + local INFO=$(cat << EOT +{ + "distribution": "$OGLIVEDIST", + "kernel": "$OGLIVEKRNL", + "architecture": "$OGLIVEARCH", + "revision": "$OGLIVEREV", + "directory": "$DIR", + "iso": "$OGLIVEISO" +} +EOT + ) + + # Devolver la información en formato JSON. + echo "$INFO" +} + +# Function to check the status of services +function check_services_status() { + local SERVICES=("oglive_daemon.service" "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 oglive_daemon "${STATUS_MAP['oglive_daemon.service']}" \ + --arg tftpboot "${STATUS_MAP['tftpd-hpa.service']}" \ + --arg nginx "${STATUS_MAP['nginx.service']}" \ + '{ + oglive_daemon: $oglive_daemon, + tftpboot: $tftpboot, + nginx: $nginx + }') + + echo "$json_output" +} + +# Main progrram. + +# Access control. +[ -r $OPENGNSYS/www/controlacceso.php ] && ACCESS="web" +[ "$USER" = "root" ] && ACCESS="root" +[ -z "$ACCESS" ] && raiseError access "Need to be root." +# Check dependencies. +which sponge &>/dev/null || raiseError notfound "Need to install \"moreutils\"." +# Commands control. +shopt -s extglob +case "$ACCESS" in + root) CMDS='+(help|version|convert|config|check|list|show|search|download|install|uninstall|get-default|set-default|rebuild|assign|disk_usage|list_installed_oglives|get_info|get_default|set_default|check_services_status)' ;; + web) CMDS='+(list|show|search|get-default)' ;; +esac +case "$1" in + $CMDS) COMMAND="${1/-/}"; shift; $COMMAND "$@" ;; + *) raiseError usage ;; +esac + +exit $? + diff --git a/bin/phpunit b/bin/phpunit new file mode 100755 index 0000000..692bacc --- /dev/null +++ b/bin/phpunit @@ -0,0 +1,23 @@ +#!/usr/bin/env php += 80000) { + require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit'; + } else { + define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php'); + require PHPUNIT_COMPOSER_INSTALL; + PHPUnit\TextUI\Command::main(); + } +} else { + if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) { + echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n"; + exit(1); + } + + require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php'; +} diff --git a/bin/setsmbpass b/bin/setsmbpass new file mode 100755 index 0000000..44a7004 --- /dev/null +++ b/bin/setsmbpass @@ -0,0 +1,142 @@ +#!/bin/bash + +#/** +# setsmbpass +#@file setsmbpass [ogLive] +#@brief Cambia la contraseña del usuario del cliente para acceder a los servicios Samba. +#@warning Se modifica el Initrd del cliente y se cambia la clave en el servidor. +#@warning No se modifica el usuario de acceso (usuario "opengnsys"). +#@version 1.0.2 - Versión inicial. +#@author Ramón M. Gómez - ETSII Univ. Sevilla +#@date 2011-07-28 +#@version 1.1.0 - Soporte para varios clientes ogLive. +#@author Ramón M. Gómez - ETSII Univ. Sevilla +#@date 2017-06-20 +#*/ ## + + +# Variables. +PROG=$(basename "$0") +PATH=$PATH:$(dirname "$(realpath "$0")") +OPENGNSYS=${OPENGNSYS:-"/opt/opengnsys"} +OGCFGFILE=$OPENGNSYS/etc/opengnsys.json +SAMBAUSER="opengnsys" # Usuario por defecto. +TFTPDIR=$OPENGNSYS/tftpboot +INITRD=oginitrd.img +TMPDIR=/tmp/oglive$$ +let CHANGES=0 + +# Control básico de errores. +if [ "$USER" != "root" ]; then + echo "$PROG: Error: solo ejecutable por root" >&2 + exit 1 +fi +case $# in + 0) # Cambios en todos los clientes ogLive instalados. + if which oglivecli &>/dev/null; then + LIST=$(oglivecli list | awk '{print $2}') + else + LIST="ogclient" + fi ;; + 1) # Cambios en único ogLive (AVISO: puede crear inconsistencias con otros ogLive). + LIST="$1" ;; + *) # Error de formato. + echo "$PROG: Error de ejecución" >&2 + echo "Formato: $PROG ogLive" + exit 1 ;; +esac + +# Recuperar eco de consola si se corta el proceso. +trap "stty echo 2>/dev/null" KILL +# Buscar todos los clients ogLive instalados. +for OGLIVE in $LIST; do + # Crear clave para usuario de acceso a los recursos. + CLIENTINITRD="$TFTPDIR/$OGLIVE/$INITRD" + if [ -r "$CLIENTINITRD" ]; then + if [ -z "$SAMBAPASS" ]; then + # Obtener clave del teclado sin eco en pantalla. + stty -echo 2>/dev/null + echo -n "Clave del usuario Samba: " + read -r SAMBAPASS + # Solo se deben aceptar números y letras para la clave de acceso. + if [[ "$SAMBAPASS" =~ [^a-zA-Z0-9] ]]; then + echo + echo "$PROG: Error: la clave solo debe contener caracteres alfanuméricos" >&2 + stty echo 2>/dev/null + exit 2 + fi + echo + # Obtener confirmación clave sin eco en pantalla. + echo -n "Confirmar clave: " + read -r SAMBAPASS2 + echo + stty echo 2>/dev/null + if [ "$SAMBAPASS" != "$SAMBAPASS2" ]; then + echo "$PROG: Error: las claves no coinciden" >&2 + exit 2 + fi + fi + # Editar la parte de acceso del cliente: + # descomprimir Initrd, sustituir clave y recomprimir Initrd). + echo "Configurando cliente \"$OGLIVE\" ..." + mkdir -p $TMPDIR + cd $TMPDIR || { echo "Error: no se pudo cambiar al directorio temporal."; exit 3; } + + # Verificar si el archivo es gzip o lz4 antes de descomprimir. + if file "$CLIENTINITRD" | grep -q "gzip compressed data"; then + if ! gzip -dc "$CLIENTINITRD" | cpio -im; then + echo "Error: No se pudo descomprimir y extraer $CLIENTINITRD con gzip." + exit 4 + fi + COMPRESS_CMD="gzip -9c" + elif file "$CLIENTINITRD" | grep -q "LZ4 compressed data"; then + if ! lz4 -d "$CLIENTINITRD" | cpio -im; then + echo "Error: No se pudo descomprimir y extraer $CLIENTINITRD con lz4." + exit 4 + fi + COMPRESS_CMD="lz4 -c" + else + echo "Error: $CLIENTINITRD no está en formato gzip o lz4." + exit 4 + fi + if [ -f scripts/ogfunctions ]; then + sed -i "s/OPTIONS=\(.*\)user=\w*\(.*\)pass=\w*\(.*\)/OPTIONS=\1user=$SAMBAUSER\2pass=$SAMBAPASS\3/" scripts/ogfunctions + # TEMPORAL: solución ticket 554, actualizar cliente en caché (ogLive r3257). + sed -i "s/busybox reboot/reboot/" scripts/ogfunctions + # FIN CÓDIGO TEMPORAL. + # Ticket 565, preparar acceso Rsync cliente. + echo "$SAMBAPASS" > scripts/passrsync + # Guardar tokens de seguridad. + cat << EOT > scripts/client.cfg +CLIENTID=$(jq -r .client.id $OGCFGFILE) +CLIENTSECRET=$(jq -r .client.secret $OGCFGFILE) +EOT + chown root.root scripts/passrsync scripts/client.cfg + chmod 400 scripts/passrsync scripts/client.cfg + # Generar Initrd del cliente. + if ! find . | cpio -H newc -oa | $COMPRESS_CMD > "$CLIENTINITRD"; then + echo "Error: No se pudo recomprimir $CLIENTINITRD." + exit 5 + fi + else + echo "$PROG: Aviso: no se ha modificado la clave del cliente \"$OGLIVE\"." + fi + rm -fr $TMPDIR + # Calcular suma de comprobación. + md5sum "$CLIENTINITRD" | cut -f1 -d" " > "$CLIENTINITRD.sum" + let CHANGES++ + else + echo "$PROG: Cliente \"$OGLIVE\" no accesible." + fi +done +if [[ $CHANGES != 0 ]]; then + # Ticket 565, preparar acceso Rsync servidor. + echo "$SAMBAUSER:$SAMBAPASS" > /etc/rsyncd.secrets + chown root.root /etc/rsyncd.secrets + chmod 600 /etc/rsyncd.secrets + # Cambiar clave Samba. + echo -ne "$SAMBAPASS\n$SAMBAPASS\n" | smbpasswd -a -s $SAMBAUSER +else + echo "$PROG: Aviso: no se ha modificado la clave de ningún cliente." +fi + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7f0c85e --- /dev/null +++ b/composer.json @@ -0,0 +1,107 @@ +{ + "type": "project", + "license": "proprietary", + "minimum-stability": "stable", + "prefer-stable": true, + "require": { + "php": ">=7.2.0", + "ext-ctype": "*", + "ext-iconv": "*", + "doctrine/annotations": "^1.6", + "doctrine/doctrine-bundle": "^2.0", + "doctrine/doctrine-migrations-bundle": "^3.0", + "doctrine/orm": "^2.7", + "phpdocumentor/reflection-docblock": "^5.0", + "phpstan/phpdoc-parser": "^0.4", + "zircote/swagger-php": "3.*", + "symfony/runtime": "5.*", + "symfony/asset": "5.*", + "symfony/console": "5.*", + "symfony/doctrine-messenger": "5.*", + "symfony/dotenv": "5.*", + "symfony/expression-language": "5.*", + "symfony/flex": "^1.17", + "symfony/form": "5.*", + "symfony/framework-bundle": "5.*", + "symfony/http-client": "5.*", + "symfony/intl": "5.*", + "symfony/mailer": "5.*", + "symfony/mime": "5.*", + "symfony/monolog-bundle": "^3.0", + "symfony/notifier": "5.*", + "symfony/process": "5.*", + "symfony/property-access": "5.*", + "symfony/property-info": "5.*", + "symfony/security-bundle": "5.*", + "symfony/serializer": "5.*", + "symfony/string": "5.*", + "symfony/translation": "5.*", + "symfony/twig-bundle": "5.*", + "symfony/validator": "5.*", + "symfony/web-link": "5.*", + "symfony/yaml": "5.*", + "twig/extra-bundle": "^2.12|^3.0", + "twig/twig": "^2.12|^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5", + "symfony/browser-kit": "5.*", + "symfony/css-selector": "5.*", + "symfony/debug-bundle": "5.*", + "symfony/maker-bundle": "^1.0", + "symfony/phpunit-bridge": "^5.0", + "symfony/stopwatch": "5.*", + "symfony/web-profiler-bundle": "5.*" + }, + "config": { + "platform": { + "php": "7.2.24" + }, + "allow-plugins": { + "composer/package-versions-deprecated": true, + "symfony/flex": true, + "symfony/runtime": true + }, + "optimize-autoloader": true, + "preferred-install": { + "*": "dist" + }, + "sort-packages": true + }, + "autoload": { + "psr-4": { + "App\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "App\\Tests\\": "tests/" + } + }, + "replace": { + "symfony/polyfill-ctype": "*", + "symfony/polyfill-iconv": "*", + "symfony/polyfill-php72": "*" + }, + "scripts": { + "auto-scripts": { + "cache:clear": "symfony-cmd", + "assets:install %PUBLIC_DIR%": "symfony-cmd" + }, + "post-install-cmd": [ + "@auto-scripts" + ], + "post-update-cmd": [ + "@auto-scripts" + ] + }, + "conflict": { + "symfony/symfony": "*" + }, + "extra": { + "symfony": { + "allow-contrib": false, + "require": "5.*" + } + } +} diff --git a/config/bundles.php b/config/bundles.php new file mode 100644 index 0000000..aeee1c2 --- /dev/null +++ b/config/bundles.php @@ -0,0 +1,15 @@ + ['all' => true], + Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], + Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], + Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], + Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], + Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], + Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], + Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], + App\OgBootBundle\OgBootBundle::class => ['all' => true], +]; diff --git a/config/routes/annotations.yaml b/config/routes/annotations.yaml new file mode 100644 index 0000000..3d945f9 --- /dev/null +++ b/config/routes/annotations.yaml @@ -0,0 +1,6 @@ +controllers: + resource: ../../src/OgBootBundle/Controller/ + type: annotation +kernel: + resource: ../../src/Kernel.php + type: annotation diff --git a/config/services.yaml b/config/services.yaml new file mode 100644 index 0000000..d8590f3 --- /dev/null +++ b/config/services.yaml @@ -0,0 +1,27 @@ +# This file is the entry point to configure your own services. +# Files in the packages/ subdirectory configure your dependencies. + +# Put parameters here that don't need to change on each machine where the app is deployed +# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration +parameters: + +services: + # default configuration for services in *this* file + _defaults: + autowire: true # Automatically injects dependencies in your services. + autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. + + # makes classes in src/ available to be used as services + # this creates a service per class whose id is the fully-qualified class name + App\: + resource: '../src/' + exclude: + - '../src/DependencyInjection/' + - '../src/Entity/' + - '../src/Kernel.php' + + # add more service definitions when explicit configuration is needed + # please note that last definitions always *replace* previous ones + App\OgBootBundle\Controller\: + resource: '../src/OgBootBundle/Controller' + tags: ['controller.service_arguments'] diff --git a/docs/ogboot-ogcore-communication-design.md b/docs/ogboot-ogcore-communication-design.md new file mode 100644 index 0000000..5988384 --- /dev/null +++ b/docs/ogboot-ogcore-communication-design.md @@ -0,0 +1,795 @@ +## Diseño de la Lógica y Comunicación entre los Endpoints del Componente `ogboot` y el Servidor `ogCore` + +### Introducción + +El componente `ogboot` se encarga de la gestión y configuración de archivos de arranque (iPXE) y plantillas en un entorno de despliegue de imágenes. El servidor `ogCore` es el núcleo que interactúa con `ogboot` para realizar operaciones de administración y configuración. Este documento describe la lógica y la comunicación entre los endpoints del componente `ogboot` y el servidor `ogCore`. + +### Endpoints del Componente `ogboot` + + +#### Endpoints del Recurso `oglive` + +1. **Ver todos los ogLives instalados** + - **URL**: `/ogboot/v1/oglives` + - **Método HTTP**: GET + - **Descripción**: Obtiene información sobre todos los ogLives instalados. + - **Respuesta**: + ```json + [ + "ogLive-5.0.0-r20190605", + "ogLive-5.11.0-r20210413", + "ogLive-5.13.0-27-r20210706" + ] + ``` + + +2. **Obtener Información de un ogLive** + - **URL**: `/ogboot/v1/oglives/{uuid}` + - **Método HTTP**: GET + - **Descripción**: Obtiene información sobre un cliente ogLive instalado utilizando su nombre. + - **Parámetros**: + - `uuid` (string): uuid del ogLive. + - **Respuesta**: + ```json + { + "distribution": "uuid", + "distribution": "focal", + "kernel": "5.13.0-27", + "architecture": "amd64", + "revision": "r20210706.5b4bf5f", + "directory": "ogLive-focal-5.13.0-27-beta-amd64-r20210706.5b4bf5f", + "iso": "ogLive-focal-5.13.0-27-beta-amd64-r20210706.5b4bf5f.iso" + } + ``` + - **Respuestas**: + - **200 OK**: Información del ogLive obtenida exitosamente. + - **404 Not Found**: ogLive no encontrado. + +3. **Obtener Información del ogLive Predeterminado** + - **URL**: `/ogboot/v1/oglives/default` + - **Método HTTP**: GET + - **Descripción**: Obtiene información sobre el cliente ogLive predeterminado. + - **Ejemplo de Solicitud**: + ```bash + curl -X GET -H "Authorization: $API_KEY" http://example.com/ogboot/v1/oglives/default + ``` + - **Respuesta**: + ```json + { + "distribution": "focal", + "kernel": "5.13.0-27", + "architecture": "amd64", + "revision": "r20210706.5b4bf5f", + "directory": "ogLive-focal-5.13.0-27-beta-amd64-r20210706.5b4bf5f", + "iso": "ogLive-focal-5.13.0-27-beta-amd64-r20210706.5b4bf5f.iso" + } + ``` + + - **Respuestas**: + - **200 OK**: Información del ogLive predeterminado obtenida exitosamente. + - **500 Internal Server Error**: Error al obtener la información del ogLive predeterminado. + + +4. **Establecer ogLive Predeterminado** + - **URL**: `/ogboot/v1/oglives/default/{uuid}` + - **Método HTTP**: POST + - **Descripción**: Establece un cliente ogLive como predeterminado utilizando su nombre. + - **Parámetros**: + - `uuid` (string): uuid del ogLive a establecer como predeterminado. + - **Respuestas**: + - **200 OK**: ogLive establecido como predeterminado exitosamente. + - **404 Not Found**: ogLive no encontrado. + - **500 Internal Server Error**: Error al establecer el ogLive como predeterminado. + + + +5. **Ver la lista de ogLives disponibles para descargar** + - **URL**: `/ogboot/v1/oglives/isos` + - **Método HTTP**: GET + - **Respuesta**: + ```json + [ + { + "id": "1", + "filename": "ogLive-focal-5.13.0-27-beta-amd64-r20210706.5b4bf5f.iso", + "installed": false, + "compatible": true + }, + { + "id": "2", + "filename": "ogLive-focal-5.11.0-22-generic-amd64-r20210413.992ebb9.iso", + "installed": false, + "compatible": true + }, + { + "id": "3", + "filename": "ogLive-focal-5.8.0-50-generic-amd64-r20210413.992ebb9.iso", + "installed": false, + "compatible": true + }, + { + "id": "4", + "filename": "ogLive-bionic-5.4.0-40-generic-amd64-r20200629.85eceaf.iso", + "installed": false, + "compatible": true + }, + + ] + + ``` + - **500 Internal Server Error**: Error en la conexión. + + +6. **Crear un ogLive** + - **URL**: `/ogboot/v1/oglives` + - **Método HTTP**: POST + - **Descripción**: Crea un nuevo ogLive utilizando el nombre del ISO proporcionado. + - **Cuerpo de la Solicitud**: + ```json + { + "isoName": "ogLive-focal-5.13.0-27-beta-amd64-r20210706.5b4bf5f.iso" + } + ``` + - **Ejemplo de Solicitud**: + ```bash + curl -X POST -H "Authorization: $API_KEY" -d '{"isoName": "ogLive-focal-5.13.0-27-beta-amd64-r20210706.5b4bf5f.iso"}' http://example.com/ogboot/v1/oglives + ``` + - **Respuestas**: + - **200 OK**: ogLive creado exitosamente. + - **400 Bad Request**: Error en los datos proporcionados. + - **500 Internal Server Error**: Error al crear el ogLive. + +7. **Eliminar un ogLive** + - **URL**: `/ogboot/v1/oglives/{uuid}` + - **Método HTTP**: DELETE + - **Descripción**: Elimina un ogLive específico utilizando su nombre. + - **Parámetros**: + - `name` (string): Nombre del ogLive. + - **Respuestas**: + - **200 OK**: ogLive eliminado exitosamente. + - **404 Not Found**: ogLive no encontrado. + - **500 Internal Server Error**: Error al eliminar el ogLive. + + + +#### Endpoints del Recurso `pxe` y `pxe-template` + +1. **Obtener Todos los Archivos de Arranque** + - **URL**: `/ogboot/v1/pxes` + - **Método HTTP**: GET + - **Descripción**: Obtiene una lista de todos los archivos de arranque disponibles. + - **Respuesta**: Lista de archivos de arranque en formato JSON. + +2. **Obtener Configuración de Arranque** + - **URL**: `/ogboot/v1/pxes/{mac}` + - **Método HTTP**: GET + - **Descripción**: Obtiene el contenido del archivo de configuración de arranque específico para un cliente utilizando su dirección MAC. + - **Respuesta**: Archivo de arranque en formato adecuado. + ```json + { + "template_name": "pxe", + "mac": "00:50:56:22:11:12", + "lang": "es_ES.UTF-8", + "ip": "192.168.2.11", + "server_ip": "192.168.2.1", + "router": "192.168.2.1", + "netmask": "255.255.255.0", + "computer_name": "pc11", + "netiface": "eth0", + "group": "Aula_virtual", + "ogrepo": "192.168.2.1", + "oglive": "192.168.2.1", + "oglog": "192.168.2.1", + "ogshare": "192.168.2.1", + "oglivedir": "ogLive", + "ogprof": "false", + "hardprofile": "", + "ogntp": "", + "ogdns": "", + "ogproxy": "", + "ogunit": "", + "resolution": "788" + } + + ``` +3. **Crear Archivo de Arranque** + - **URL**: `/ogboot/v1/pxes` + - **Método HTTP**: POST + - **Descripción**: Crea un nuevo archivo de arranque utilizando los parámetros proporcionados. + - **Cuerpo de la Solicitud**: + ```json + { + "template_name": "pxe", + "mac": "00:50:56:22:11:12", + "lang": "es_ES.UTF-8", + "ip": "192.168.2.11", + "server_ip": "192.168.2.1", + "router": "192.168.2.1", + "netmask": "255.255.255.0", + "computer_name": "pc11", + "netiface": "eth0", + "group": "Aula_virtual", + "ogrepo": "192.168.2.1", + "oglive": "192.168.2.1", + "oglog": "192.168.2.1", + "ogshare": "192.168.2.1", + "oglivedir": "ogLive", + "ogprof": "false", + "hardprofile": "", + "ogntp": "", + "ogdns": "", + "ogproxy": "", + "ogunit": "", + "resolution": "788" + } + + ``` + - **Respuesta**: Mensaje de éxito o error. + +4. **Eliminar Archivo de Arranque** + - **URL**: `/ogboot/v1/pxes/{name}` + - **Método HTTP**: DELETE + - **Descripción**: Elimina un archivo de arranque específico utilizando su dirección MAC. + - **Respuesta**: Mensaje de éxito o error. + +5. **Obtener Todas las Plantillas de Arranque** + - **URL**: `/ogboot/v1/pxe-templates` + - **Método HTTP**: GET + - **Descripción**: Obtiene una lista de todas las plantillas de arranque disponibles. + - **Respuesta**: Lista de plantillas en formato JSON. + ```json + { + "templates": [ + "pxe", + "pxe2", + "pxe_default" + ] + } + ``` +6. **Obtener Contenido de una Plantilla** + - **URL**: `/ogboot/v1/pxe-templates/{name}` + - **Método HTTP**: GET + - **Descripción**: Obtiene el contenido de una plantilla de arranque específica utilizando su nombre. + - **Respuesta**: Contenido de la plantilla en formato adecuado. + +7. **Crear Plantilla de Arranque** + - **URL**: `/ogboot/v1/pxe-templates` + - **Método HTTP**: POST + - **Descripción**: Crea una nueva plantilla de arranque utilizando los datos proporcionados. + - **Cuerpo de la Solicitud**: + ```json + { + "name_template": "pxe", + "content_template": "contenido_de_la_plantilla" + } + ``` + - **Respuesta**: Mensaje de éxito o error. + +8. **Eliminar Plantilla de Arranque** + - **URL**: `/ogboot/v1/pxe-templates/{name}` + - **Método HTTP**: DELETE + - **Descripción**: Elimina una plantilla de arranque específica utilizando su nombre. + - **Respuesta**: Mensaje de éxito o error. + + + + +## Flujos de Trabajo + +Para los nuevos flujos de trabajo, asumimos que habrá al menos una nueva tabla en ogCore para almacenar la información de los ogLives y su estado. Esta tabla permitirá a ogCore gestionar y sincronizar la información con ogBoot. + + +### Propuesta de definición de la tabla `ogLive` + + +| Atributo | Tipo de Dato | Descripción | +|----------------|----------------|-----------------------------------------------------------------| +| `id` | `SERIAL` | Identificador del ogLive que corresponde a su suma de comprobación | +| `distribution` | `VARCHAR(50)` | Nombre de la distribución del ogLive (por ejemplo, "focal"). | +| `kernel` | `VARCHAR(100)` | Versión del kernel del ogLive. | +| `architecture` | `VARCHAR(10)` | Arquitectura del ogLive (por ejemplo, "amd64"). | +| `revision` | `VARCHAR(50)` | Revisión del ogLive (por ejemplo, "r20210706"). | +| `directory` | `VARCHAR(100)` | Nombre del directorio del ogLive en el sistema de archivos. | +| `iso` | `VARCHAR(255)` | Nombre del archivo ISO del ogLive. | +| `is_default` | `BOOLEAN` | Indica si el ogLive es el predeterminado (`true` o `false`). | + +### Ejemplo de Registro + +| `id` | `distribution` | `kernel` | `architecture` | `revision` | `directory` | `iso` | `is_default` | +|------|----------------|---------------------|----------------|-------------|--------------------------|-------------------------------------------------------|--------------| +| 1 | focal | 5.13.0-27-beta | amd64 | r20210706 | ogLive-5.13.0-r20210706 | ogLive-focal-5.13.0-27-beta-amd64-r20210706.5b4bf5f.iso | true | + + + + +### Flujo de trabajo para instalar un `ogLive` + +#### 1. Consultar los `ogLives` Instalados + +- **Descripción**: Obtener la lista de `ogLives` que están actualmente instalados en el sistema. +- **Interacción en la Web**: + - El usuario navega a la sección de `ogLives` en el panel de administración. + - Hace clic en "Ver `ogLives` instalados". + - El sistema realiza una llamada a la API para obtener la lista de `ogLives` instalados. + +**Realización**: Puede realizarse directamente desde **`ogCore`** mediante una consulta a la base de datos `oglives`. + +**Consulta SQL**: +```sql +SELECT * FROM oglives; +``` + +**Endpoint**: +- **`ogBoot`**: `/ogboot/v1/oglives` +- **Método**: `GET` + +#### 2. Consultar los `ogLives` disponibles para descargar + +- **Descripción**: Obtener una lista de `ogLives` disponibles para descargar desde el servidor. +- **Interacción en la Web**: + - El usuario selecciona "Descargar nuevos `ogLives`" en la interfaz. + - Aparece una lista de `ogLives` disponibles, obtenida mediante una consulta al servidor. + +**Realización**: Necesita comunicación con **`ogBoot`**. Nota: Este proceso requiere de una consulta a Trac (o web) que se podría llevar a cabo desde el ogCore. + +**Endpoint**: +- **`ogBoot`**: `/ogboot/v1/isos` +- **Método**: `GET` + +#### 3. Instalar un `ogLive` + +- **Descripción**: Instalar un `ogLive` seleccionado en el sistema. +- **Interacción en la Web**: + - El usuario selecciona un `ogLive` de la lista de disponibles y hace clic en "Instalar". + - El sistema muestra un cuadro de confirmación, y al confirmar, se inicia el proceso de instalación. + +**Realización**: Requiere comunicación con **`ogBoot`** para iniciar la instalación. Primero, se construye el JSON con los parámetros necesarios. La inserción en la base de datos `ogCore` solo se realiza después de que la instalación en `ogBoot` sea confirmada como exitosa. + +- **Proceso de Instalación**: + 1. **Generación de JSON**: `ogCore` genera un JSON con los detalles del `ogLive`, incluyendo un UUID temporal. + 2. **Solicitud de Instalación**: + - **Endpoint**: `/ogboot/v1/oglives` + - **Método**: `POST` + - **Cuerpo de la Solicitud**: + ```json + { + "uuid": "550e8400-e29b-41d4-a716-446655440000", + "directory": "ogLive-5.11.0-r20210413", + "distribution": "focal", + "kernel": "5.11.0-22-generic", + "architecture": "amd64", + "revision": "r20210413", + "iso": "ogLive-focal-5.11.0-22-generic-amd64-r20210413.992ebb9.iso" + } + ``` + 3. **Validación de Instalación**: `ogBoot` intenta instalar el `ogLive` y devuelve un estado de éxito o fallo. + 4. **Actualización de Base de Datos**: + - **Instalación Exitosa**: Si `ogBoot` confirma el éxito, `ogCore` inserta el nuevo `ogLive` en la base de datos: + ```sql + INSERT INTO oglives (uuid, distribution, kernel, architecture, revision, directory, iso, is_default) + VALUES ('550e8400-e29b-41d4-a716-446655440000', 'focal', '5.11.0-22-generic', 'amd64', 'r20210413', 'ogLive-5.11.0-r20210413', 'ogLive-focal-5.11.0-22-generic-amd64-r20210413.992ebb9.iso', false); + ``` + - **Instalación Fallida**: Si `ogBoot` reporta un fallo, no se realiza ninguna inserción y se maneja el error adecuadamente en la interfaz de usuario. + +#### 4. Revisar un `ogLive` Instalado + +- **Descripción**: Obtener detalles sobre un `ogLive` específico que está instalado. +- **Interacción en la Web**: + - El usuario selecciona un `ogLive` de la lista de instalados para ver detalles. + - Se muestra la información detallada del `ogLive` seleccionado. + +**Realización**: Puede realizarse desde **`ogCore`** mediante una consulta a la base de datos `oglives`. + +**Consulta SQL**: +```sql +SELECT * FROM oglives WHERE directory = 'ogLive-5.13.0-r20210706'; +``` + + +### Flujo de trabajo para cambiar el `oglive` por defecto + +#### 1. Consultar los `ogLives` Instalados + +- **Descripción**: Obtener la lista de `ogLives` que están actualmente instalados en el sistema. +- **Interacción en la Web**: + - El usuario navega a la sección de `ogLives` en el panel de administración. + - Hace clic en "Ver `ogLives` instalados". + - El sistema realiza una llamada a la API para obtener la lista de `ogLives` instalados. + +**Realización**: Puede realizarse directamente desde **`ogCore`** mediante una consulta a la base de datos `oglives`. Lo ideal sería hacerlo hacia **`ogBoot`** ya que tiene la fuente de información fidedigna del estado de los `ogLives` en el componente. + +**Consulta SQL**: +```sql +SELECT * FROM oglives; +``` + +**Endpoint**: +- **`ogBoot`**: `/ogboot/v1/oglives` +- **Método**: `GET` + +#### 2. Ver el `ogLive` por Defecto + +- **Descripción**: Obtener el `ogLive` que está configurado como predeterminado. +- **Interacción en la Web**: + - El usuario navega a la sección de configuración de `ogLives` en el panel de administración. + - Hace clic en en el `ogLive` que está marcado por defecto. + - El sistema realiza una consulta a la base de datos para obtener el `ogLive` predeterminado. + +**Realización**: Puede realizarse desde **`ogCore`** mediante una consulta a la base de datos `oglives`. + +**Consulta SQL**: +```sql +SELECT * FROM oglives WHERE directory = 'ogLive-5.13.0-r20210706'; +``` + + + +#### 3. Asignar `ogLive` por Defecto + +- **Descripción**: Configurar un `ogLive` instalado como el predeterminado. +- **Interacción en la Web**: + - El usuario selecciona un `ogLive` de la lista de instalados. + - Hace clic en "Configurar como `ogLive` por defecto". + - Tras la confirmación, el sistema envía una solicitud para actualizar el `ogLive` por defecto. + - El sistema actualiza la base de datos y comunica el cambio a `ogBoot`. + +**Realización**: Requiere comunicación con **`ogBoot`** para modificar el `ogLive` por defecto en el TFTP boot del **`ogBoot`**. + +**Consulta SQL**: +```sql +SELECT * FROM oglives WHERE directory = 'ogLive-5.13.0-r20210706'; +``` + +**Endpoint**: +- **`ogBoot`**: `/ogboot/v1/oglives/default/550e8400-e29b-41d4-a716-446655440000` +- **Método**: `POST` + +**Actualizar Base de Datos**: +- Tras la actualización en `ogBoot`, se debe actualizar la base de datos para reflejar el nuevo `ogLive` por defecto: + ```sql + UPDATE oglives SET is_default = false WHERE is_default = true; + UPDATE oglives SET is_default = true WHERE uuid = '550e8400-e29b-41d4-a716-446655440000'; + ``` + + +### Flujo de trabajo para desinstalar un `ogLive` + +#### 1. Consultar los `ogLives` Instalados + +- **Descripción**: Obtener la lista de `ogLives` que están actualmente instalados en el sistema. +- **Interacción en la Web**: + - El usuario navega a la sección de `ogLives` en el panel de administración. + - Hace clic en "Ver `ogLives` instalados". + - El sistema realiza una llamada a la API para obtener la lista de `ogLives` instalados. + +**Realización**: Puede realizarse directamente desde **`ogCore`** mediante una consulta a la base de datos `oglives`. + +**Consulta SQL**: +```sql +SELECT * FROM oglives; +``` + +**Endpoint**: +- **`ogBoot`**: `/ogboot/v1/oglives` +- **Método**: `GET` + + +#### 2. Desinstalar `ogLive` + +- **Descripción**: Iniciar el proceso de desinstalación del `ogLive` seleccionado en `ogBoot`. +- **Interacción en la Web**: + - En la lista de `ogLives` instalados, el usuario elige el `ogLive` a desinstalar. + - Hace clic en el botón "Desinstalar" junto al `ogLive` seleccionado. + - El sistema muestra un cuadro de confirmación preguntando si el usuario está seguro de desinstalar el `ogLive`. + - El usuario hace clic en "Confirmar" para proceder. + - El sistema envía una solicitud a `ogBoot` para desinstalar el `ogLive`. + +**Realización**: +- **Endpoint**: `/ogboot/v1/oglives/550e8400-e29b-41d4-a716-446655440000` +- **Método**: `DELETE` +- **Validación de Instalación**: `ogBoot` intenta desinstalar el `ogLive` y devuelve un estado de éxito o fallo. +- **Actualización de Base de Datos**: + - **Desinstalación Exitosa**: Si `ogBoot` confirma el éxito, `ogCore` elimina el `ogLive` en la base de datos: + ```sql + DELETE FROM oglives WHERE uuid = '550e8400-e29b-41d4-a716-446655440000'; + ``` + - **Instalación Fallida**: Si `ogBoot` reporta un fallo, no se realiza ningún borrado y se maneja el error adecuadamente en la interfaz de usuario. + + +### Flujo de trabajo para corregir las incongruencias entre `ogCore` y `ogBoot` + + + +#### 8. Revisar estado de ogboot + +- **Descripción**: Comparar el estado actual de `ogboot` con la base de datos de `ogCore`, generando un informe de discrepancias. +- **Realización**: Requiere comunicación con **`ogBoot`** para obtener el estado actual de los `ogLives` instalados, el `ogLive` por defecto, y las plantillas de arranque (`pxes`). Luego, `ogCore` compara estos datos con su base de datos y devuelve un informe de discrepancias. + +**Endpoint**: +- **`ogBoot`**: `/ogboot/v1/oglive/status` +- **Método**: `GET` +- **Resultado de la Solicitud**: + ```json + { + "installed_oglives": [ + { + "name": "ogLive-5.13.0-r20210706", + "kernel": "5.13.0-27-beta", + "revision": "r20210706", + "default": true + }, + { + "name": "ogLive-5.11.0-r20210413", + "kernel": "5.11.0-22-generic", + "revision": "r20210413", + "default": false + } + ], + "installed_pxe_templates": [ + "default.ipxe", + "template1.ipxe" + ] + } + ``` + +**Informe de Discrepancias** (Ejemplo): +```json +{ + "discrepancies": { + "oglives": [ + { + "expected": { + "name": "ogLive-5.13.0-r20210706", + "default": true + }, + "actual": { + "name": "ogLive-5.11.0-r20210413", + "default": true + } + } + ], + "pxe_templates": [ + { + "expected": "default.ipxe", + "actual": "template1.ipxe" + } + ] + } +} +``` + + +#### 9. Sincronizar Datos de `ogboot` con `ogCore` + +- **Descripción**: `ogCore` sincroniza su base de datos con el estado actual de `ogboot`. Este proceso solo actualiza las discrepancias detectadas. +- **Realización**: Requiere comunicación con **`ogBoot`** para obtener el estado actual. Luego, `ogCore` actualiza su base de datos con cualquier cambio detectado en los `ogLives` instalados, el `ogLive` por defecto, y las plantillas de arranque (`pxes`). + +**Endpoint**: +- **`ogBoot`**: `/ogboot/v1/sync` +- **Método**: `GET` +- **Resultado de la Solicitud**: + ```json + { + "installed_oglives": [ + { + "name": "ogLive-5.13.0-r20210706", + "kernel": "5.13.0-27-beta", + "revision": "r20210706", + "default": true + }, + { + "name": "ogLive-5.11.0-r20210413", + "kernel": "5.11.0-22-generic", + "revision": "r20210413", + "default": false + } + ], + "installed_pxe_templates": [ + "default.ipxe", + "template1.ipxe" + ] + } + ``` + + + + +### Flujo de Trabajo: `pxe y pxe-template` + +#### NOTA + +En la implementación actual de OpenGnsys, se realiza una consulta SQL a la tabla `ordenadores`, haciendo joins con tablas como `aulas`, `centros`, `entidades`, etc. (consultar fichero `opengnsys/server/bin/setclientmode`) para obtener todos los parámetros necesarios para la creación de archivos PXE. Es necesario estudiar si existe la necesidad de crear una tabla `pxe` para almacenar estos parámetros. En caso de decidirse la creación de dicha tabla, podría tener los siguientes atributos: + +- `template_name` (nombre de la plantilla) +- `mac` (dirección MAC) +- `lang` (idioma) +- `ip` (dirección IP) +- `server_ip` (dirección IP del servidor) +- `router` (router) +- `netmask` (máscara de red) +- `computer_name` (nombre del equipo) +- `netiface` (interfaz de red) +- `group` (grupo) +- `ogrepo` (IP del repositorio) +- `oglive` (IP del servidor en vivo) +- `oglog` (IP del servidor de logs) +- `ogshare` (IP del servidor compartido) +- `oglivedir` (directorio en vivo) +- `ogprof` (es profesor) +- `hardprofile` (perfil de hardware) +- `ogntp` (servidor NTP) +- `ogdns` (servidor DNS) +- `ogproxy` (servidor proxy) +- `ogunit` (directorio de unidad) +- `resolution` (resolución de pantalla) + +--- + +#### NOTA 2 + +¿Tiene sentido hacer una tabla para las plantillas pxe-templates? Solo tendrian dos atributos: template-name y content. ¿No sería mejor que quedasen guardadas en el ogboot y se usen cuando se vaya a crear un fichero de arranque? + + +Para estos flujos asumiremos que NO se han creado tablas a mayores de pxe ni de pxe-templates. Este es el flujo original que se produce actualmente en ogboot. + + + +1. **Obtener Todos los Archivos de Arranque** + - **URL**: `/ogboot/v1/pxes` + - **Método HTTP**: GET + - **Cuerpo de la Solicitud**: + ```json + { + { + "boot_files": [ + "01-00:50:56:20:69:11", + "01-00:50:56:20:69:12" + ] + } + } + ``` + + +2. **Obtener Configuración de Arranque por MAC** + - **URL**: `/ogboot/v1/pxes/{mac}` + - **Método HTTP**: GET + - **Cuerpo de la Solicitud**: + - **Respuesta**: + ```json + { + "template_name": "pxe", + "mac": "00:50:56:22:11:12", + "lang": "es_ES.UTF-8", + "ip": "192.168.2.11", + "server_ip": "192.168.2.1", + "router": "192.168.2.1", + "netmask": "255.255.255.0", + "computer_name": "pc11", + "netiface": "eth0", + "group": "Aula_virtual", + "ogrepo": "192.168.2.1", + "oglive": "192.168.2.1", + "oglog": "192.168.2.1", + "ogshare": "192.168.2.1", + "oglivedir": "ogLive", + "ogprof": "false", + "hardprofile": "", + "ogntp": "", + "ogdns": "", + "ogproxy": "", + "ogunit": "", + "resolution": "788" + } + + ``` + +3. **Obtener Todas las Plantillas de Arranque** + - **URL**: `/ogboot/v1/pxe-templates` + - **Método HTTP**: GET + - **Respuesta**: + ```json + { + "templates": [ + "pxe", + "pxe_default" + ] + } + ``` +4. **Crear Plantilla de Arranque** + - **URL**: `/ogboot/v1/pxes-templates` + - **Método HTTP**: POST + - **Cuerpo de la Solicitud**: + ```json + { + "name_template": "pxe2", + "content_template": "#!ipxe\\nset timeout 0\\nset timeout-style hidden\\n\\nset ISODIR ogLive\\nset default 0\\nset kernelargs INFOHOST\\nkernel tftp://SERVERIP/ogLive/ogvmlinuz ${kernelargs}\\ninitrd tftp://SERVERIP/ogLive/oginitrd.img\\nboot" + } + ``` + - **Respuesta**: + ```json + { + "message": "Plantilla creada exitosamente.", + "template": "#!ipxe\\nset timeout 0\\nset timeout-style hidden\\n\\nset ISODIR ogLive\\nset default 0\\nset kernelargs INFOHOST\\nkernel tftp://SERVERIP/ogLive/ogvmlinuz ${kernelargs}\\ninitrd tftp://SERVERIP/ogLive/oginitrd.img\\nboot" + } + ``` + +5. **Obtener Todas las Plantillas de Arranque** + - **URL**: `/ogboot/v1/pxe-templates` + - **Método HTTP**: GET + - **Respuesta**: + ```json + { + "templates": [ + "pxe", + "pxe2" + "pxe_default" + ] + } + ``` +6. **Crear archivo de Arranque** + - **URL**: `/ogboot/v1/pxes` + - **Método HTTP**: POST + - **Respuesta**: + ```json + { + "template_name": "pxe2", + "mac": "00:50:56:22:11:13", + "lang": "es_ES.UTF-8", + "ip": "192.168.2.11", + "server_ip": "192.168.2.1", + "router": "192.168.2.1", + "netmask": "255.255.255.0", + "computer_name": "pc13", + "netiface": "eth0", + "group": "Aula_virtual", + "ogrepo": "192.168.2.1", + "oglive": "192.168.2.1", + "oglog": "192.168.2.1", + "ogshare": "192.168.2.1", + "oglivedir": "ogLive", + "ogprof": "false", + "hardprofile": "", + "ogntp": "", + "ogdns": "", + "ogproxy": "", + "ogunit": "", + "resolution": "788" + } + ``` +7. **Obtener Todos los Archivos de Arranque** + - **URL**: `/ogboot/v1/pxes` + - **Método HTTP**: GET + - **Cuerpo de la Solicitud**: + ```json + { + { + "boot_files": [ + "01-00:50:56:20:69:11", + "01-00:50:56:20:69:12", + "01-00:50:56:20:69:13" + ] + } + } + ``` + +8. **Borrado de un fichero de arranque** + - **URL**: `/ogboot/v1/pxes/{mac}` + - **Método HTTP**: DELETE + - **Respuesta**: + ```json + { + "message": "El archivo de arranque se eliminó correctamente" + } + ``` + +9. **Borrado de una plantilla** + - **URL**: `/ogboot/v1/pxe-templates/{name}` + - **Método HTTP**: DELETE + - **Respuesta**: + ```json + { + "message": "Plantilla eliminada correctamente." + } + ``` + + diff --git a/lib/ogfunctions.sh b/lib/ogfunctions.sh new file mode 100755 index 0000000..51de6b7 --- /dev/null +++ b/lib/ogfunctions.sh @@ -0,0 +1,65 @@ +#!/bin/bash +#/** +#@file ogfunctions.sh +#@brief Generic functions for OpenGnsys Server and OpenGnsys Repository. +#@version 1.1.1 - Initial version +#@author Ramón M. Gómez, ETSII Universidad de Sevilla +#@date 2017-10-08 +#*/ + + +# Showing an error message. +function raiseError() { + case "$1" in + usage) + echo "$PROG: Usage error: Type \"$PROG help\"" >&2 + exit 1 ;; + notfound) + echo "$PROG: Resource not found: $2" >&2 + exit 2 ;; + access) + echo "$PROG: Access error: $2" >&2 + exit 3 ;; + download) + echo "$PROG: Download error: $2" >&2 + exit 4 ;; + *) + echo "$PROG: Unknown error" >&2 + exit 1 ;; + esac +} + +# Showing help message. +function help() { + [ -n "$1" ] && DESCRIPTION="$1" || DESCRIPTION=$(grep "^#@brief" "$0" | cut -f2- -d" ") + shift + if [ -n "$1" ]; then + USAGE="$1" + shift + else + USAGE=$(grep "^#@usage" "$0" | cut -f2- -d" ") + [ -n "$USAGE" ] && PARAMS=$(awk '$1=="#@param" {sub($1,""); print "\t",$0}' "$0") + fi + # Showing help. + echo "$PROG: ${DESCRIPTION:-"no description"}" + echo "Usage: ${USAGE:-"no usage info"}" + [ -n "$PARAMS" ] && echo -e "$PARAMS" + if [ -n "$*" ]; then + echo "Examples:" + while (( "$#" )); do + echo -e "\t$1" + shift + done + fi + exit 0 +} + +# Metafunction to check if JSON result exists. +JQ=$(which jq 2>/dev/null) || raiseError notfound "Need to install \"jq\"." +function jq() { + local OUTPUT + OUTPUT=$($JQ "$@") || return $? + [[ "$OUTPUT" = "null" ]] && return 1 + echo "$OUTPUT" +} + diff --git a/src/Controller/.gitignore b/src/Controller/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/Entity/.gitignore b/src/Entity/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/src/Kernel.php b/src/Kernel.php new file mode 100644 index 0000000..779cd1f --- /dev/null +++ b/src/Kernel.php @@ -0,0 +1,11 @@ +curlRequestService = $curlRequestService; + $this->logger = $logger; + } + +/*Tabla de Contenido +Obtener configuración de ogboot - GET /ogboot/config x +Obtener datos de rendimiento - GET /ogboot/status x +Mostrar información de todos los clientes ogLive instalados - GET /ogboot/oglives x +Mostrar información de un cliente ogLive instalado - GET /ogboot/oglives/{Index|Dir} x +Mostrar información del cliente ogLive predeterminado - GET /ogboot/oglives/default x +Cambiar ogLive predeterminado - POST /ogboot/oglives/default/{Index} x +Instalar nuevo cliente ogLive desde imagen descargada - POST /ogboot/oglive/{Index/iso} ---- +Desinstalar cliente ogLive y eliminar imagen - DELETE /ogboot/oglives/{Index/iso} ------- +Regenerar archivo de información de los ogLive - PUT /ogboot/oglives +Mostrar menú de descarga de imagen de ogLive - GET /ogboot/images/download X + +///////////////////////////////////////////// + +Obtener todos los archivos de arranque - GET /ogboot/pxes +Obtener archivo de arranque - GET /ogboot/clients/pxes/{mac} +Crear archivo de arranque - POST /ogboot/pxes +Eliminar archivo de arranque - DELETE /ogboot/clients/pxes +Obtener todas las plantillas - GET /ogboot/pxe-templates +Obtener contenido de la plantilla - GET /ogboot/pxe-templates/{nombre} +Crear plantilla - POST /ogboot/pxe-templates +Regenerar plantilla - PUT /ogboot/pxe-templates +*/ + + /** + * @Route("/ogboot/help", name="help", methods={"GET"}) + */ + public function help(): Response + { + + # $result = $this->curlRequestService->common_request(OG_REST_CMD_POWEROFF, POST, [OG_REST_PARAM_CLIENTS => $ips]); + $result = $this->curlRequestService->callOgLive("help"); + + if ($result) { + return new Response($result, Response::HTTP_OK); + } else { + return new Response('Failed', Response::HTTP_INTERNAL_SERVER_ERROR); + } + } + +/** + * @Route("/ogboot/v1/status", name="getStatus", methods={"GET"}) + * @OA\Get( + * path="/ogboot/v1/status", + * summary="Get ogboot status", + * @OA\Response( + * response=200, + * description="Status retrieved successfully", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="disk_usage", type="object", + * @OA\Property(property="total", type="string"), + * @OA\Property(property="used", type="string"), + * @OA\Property(property="available", type="string"), + * @OA\Property(property="percentage", type="string") + * ), + * @OA\Property(property="default_oglive", type="string"), + * @OA\Property(property="installed_oglives", type="array", @OA\Items(type="string")) + * ) + * ) + * ) + */ +public function getStatus(): Response +{ + // Call oglivecli to get disk usage + $diskUsageResult = $this->curlRequestService->callOgLive("disk_usage"); + + if (is_array($diskUsageResult) && isset($diskUsageResult['success']) && $diskUsageResult['success'] === false) { + return new JsonResponse(['error' => 'Failed to retrieve disk usage', 'details' => $diskUsageResult['error']], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + // Call oglivecli to get installed oglives and default oglives + $ogLiveConfigResult = $this->curlRequestService->callOgLive("list_installed_oglives"); + + if (is_array($ogLiveConfigResult) && isset($ogLiveConfigResult['success']) && $ogLiveConfigResult['success'] === false) { + return new JsonResponse(['error' => 'Failed to retrieve ogLive configuration', 'details' => $ogLiveConfigResult['error']], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + // Call oglivecli to get installed oglives and default oglives + $servicesStatusResult = $this->curlRequestService->callOgLive("check_services_status"); + + if (is_array($servicesStatusResult) && isset($servicesStatusResult['success']) && $servicesStatusResult['success'] === false) { + return new JsonResponse(['error' => 'Failed to retrieve services status', 'details' => $servicesStatusResult['error']], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + $response = [ + 'disk_usage' => $diskUsageResult, + 'default_oglive' => $ogLiveConfigResult['default_oglive'], + 'installed_oglives' => $ogLiveConfigResult['installed_ogLives'], + 'services_status' => $servicesStatusResult + ]; + + return new JsonResponse($response, Response::HTTP_OK); +} + + + +/** + * @Route("/ogboot/v1/oglives/isos", name="getDownloadMenu", methods={"GET"}) + * @OA\Get( + * path="/ogboot/v1/oglives/isos", + * summary="Get ogLive downloads menu", + * @OA\Response( + * response=200, + * description="Isos retrieved successfully", + * @OA\JsonContent( + * type="array", + * @OA\Items(type="object", + * @OA\Property(property="id", type="integer"), + * @OA\Property(property="filename", type="string"), + * @OA\Property(property="installed", type="boolean"), + * @OA\Property(property="compatible", type="boolean") + * ) + * ) + * ), + * @OA\Response( + * response=500, + * description="Failed to retrieve isos", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="error", type="string"), + * @OA\Property(property="details", type="string") + * ) + * ) + * ) + */ +public function getDownloadMenu(): Response +{ + $downloadsOutput = $this->curlRequestService->callOgLive("download"); + + if (is_array($downloadsOutput) && isset($downloadsOutput['success']) && $downloadsOutput['success'] === false) { + return new JsonResponse(['error' => 'Failed to retrieve downloads', 'details' => $downloadsOutput['error']], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + return new JsonResponse($downloadsOutput, Response::HTTP_OK); +} + +/** + * @Route("/ogboot/v1/oglives", name="getOglives", methods={"GET"}) + * @OA\Get( + * path="/ogboot/v1/oglives", + * summary="Get list of all installed ogLive clients", + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent( + * type="array", + * @OA\Items( + * type="object", + * @OA\Property( + * property="id", + * type="string", + * description="Unique identifier for the ogLive client, generated from ogclient.sqfs.sum", + * example="9e49a085ba74f97a81bdf9b3d0785094" + * ), + * @OA\Property( + * property="distribution", + * type="string", + * description="Distribution name of the installed ogLive client" + * ), + * @OA\Property( + * property="kernel", + * type="string", + * description="Kernel version of the installed ogLive client" + * ), + * @OA\Property( + * property="architecture", + * type="string", + * description="Architecture of the installed ogLive client" + * ), + * @OA\Property( + * property="revision", + * type="string", + * description="Revision of the installed ogLive client" + * ), + * @OA\Property( + * property="directory", + * type="string", + * description="Directory name of the installed ogLive client" + * ), + * @OA\Property( + * property="iso", + * type="string", + * description="ISO file name of the installed ogLive client" + * ) + * ) + * ) + * ), + * @OA\Response( + * response=404, + * description="No ogLive clients found" + * ) + * ) + */ +public function getOglives(): Response +{ + $directoryPath = '/opt/ogboot/tftpboot/'; + $pattern = '/^ogLive-([\w.-]+)-r(\d+)$/'; + + $ogLives = []; + + if ($handle = opendir($directoryPath)) { + error_log("Opened directory: $directoryPath"); + + while (false !== ($entry = readdir($handle))) { + error_log("Processing entry: $entry"); + + if ($entry != "." && $entry != ".." && is_dir($directoryPath . $entry)) { + if (preg_match($pattern, $entry, $matches)) { + error_log("Entry matches pattern: $entry"); + + // Evitar directorios .old + if (substr($entry, -4) === '.old') { + error_log("Ignoring old directory: $entry"); + continue; + } + + // Extraer detalles + $distribution = $matches[1]; + $revision = $matches[2]; + $directory = $entry; + $ogLivePath = $directoryPath . $entry; + + // Obtener el ID del archivo ogclient.sqfs.sum + $sumFile = $ogLivePath . '/ogclient.sqfs.sum'; + $id = ''; + if (file_exists($sumFile)) { + $id = trim(file_get_contents($sumFile)); + } + + // Obtener el nombre del archivo ISO + $iso = ''; + $oldInfoFile = $ogLivePath . '/OLDINFOFILE'; + if (file_exists($oldInfoFile)) { + $iso = trim(file($oldInfoFile, FILE_USE_INCLUDE_PATH | FILE_IGNORE_NEW_LINES)[0]); + } + + // Obtener la versión del kernel + $kernelFile = $ogLivePath . '/ogvmlinuz'; + $kernel = ''; + if (file_exists($kernelFile)) { + $fileOutput = shell_exec("file -bkr $kernelFile"); + if (preg_match('/Linux kernel.*version (\d+\.\d+\.\d+)/', $fileOutput, $kernelMatches)) { + $kernel = $kernelMatches[1]; + } + } + + // Asumir arquitectura como 'amd64'; puedes refinar esto si se necesita más lógica + $architecture = 'amd64'; + + // Construir el objeto + $ogLives[] = [ + 'id' => $id, + 'distribution' => $distribution, + 'kernel' => $kernel, + 'architecture' => $architecture, + 'revision' => $revision, + 'directory' => $directory, + 'iso' => $iso + ]; + } else { + error_log("Entry does not match pattern: $entry"); + } + } else { + error_log("Entry is not a directory: $directoryPath$entry"); + } + } + + closedir($handle); + error_log("Closed directory: $directoryPath"); + } + + if (!empty($ogLives)) { + error_log("Returning ogLives data"); + return new JsonResponse($ogLives, Response::HTTP_OK); + } else { + error_log("No ogLive clients found"); + return new JsonResponse(['error' => 'No ogLive clients found'], Response::HTTP_NOT_FOUND); + } +} + +/** + * @Route("/ogboot/v1/oglives/default", name="getOgliveDefault", methods={"GET"}) + * @OA\Get( + * path="/ogboot/v1/oglives/default", + * summary="Get information of the default ogLive client", + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent( + * type="object", + * @OA\Property( + * property="distribution", + * type="string", + * description="Distribution name of the default ogLive client" + * ), + * @OA\Property( + * property="kernel", + * type="string", + * description="Kernel version of the default ogLive client" + * ), + * @OA\Property( + * property="architecture", + * type="string", + * description="Architecture of the default ogLive client" + * ), + * @OA\Property( + * property="revision", + * type="string", + * description="Revision of the default ogLive client" + * ), + * @OA\Property( + * property="directory", + * type="string", + * description="Directory name of the default ogLive client" + * ), + * @OA\Property( + * property="iso", + * type="string", + * description="ISO file name of the default ogLive client" + * ) + * ) + * ), + * @OA\Response( + * response=500, + * description="Failed to retrieve information of the default ogLive client" + * ) + * ) + */ +public function getOgliveDefault(Request $request): Response +{ + $result = $this->curlRequestService->callOgLive("get_default"); + + if (is_array($result) && isset($result['error'])) { + return new JsonResponse(['error' => $result['error']], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + return new JsonResponse($result, Response::HTTP_OK); +} + +/** + * @Route("/ogboot/v1/oglives/{checksum}", name="getOglive", methods={"GET"}) + * @OA\Get( + * path="/ogboot/v1/oglives/{checksum}", + * summary="Get information of an installed ogLive client", + * @OA\Parameter( + * name="checksum", + * in="path", + * description="Checksum of the installed ogLive client", + * required=true, + * @OA\Schema( + * type="string", + * example="9e49a085ba74f97a81bdf9b3d0785094" + * ) + * ), + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent( + * type="object", + * @OA\Property( + * property="distribution", + * type="string", + * description="Distribution name of the installed ogLive client" + * ), + * @OA\Property( + * property="kernel", + * type="string", + * description="Kernel version of the installed ogLive client" + * ), + * @OA\Property( + * property="architecture", + * type="string", + * description="Architecture of the installed ogLive client" + * ), + * @OA\Property( + * property="revision", + * type="string", + * description="Revision of the installed ogLive client" + * ), + * @OA\Property( + * property="directory", + * type="string", + * description="Directory name of the installed ogLive client" + * ), + * @OA\Property( + * property="iso", + * type="string", + * description="ISO file name of the installed ogLive client" + * ) + * ) + * ), + * @OA\Response( + * response=404, + * description="ogLive client not found" + * ) + * ) + */ +public function getOglive(string $checksum): Response +{ + $result = $this->curlRequestService->callOgLive("get_info " . escapeshellarg($checksum)); + + if (is_array($result) && isset($result['error'])) { + return new JsonResponse(['error' => $result['error']], Response::HTTP_NOT_FOUND); + } + + return new JsonResponse($result, Response::HTTP_OK); +} + + + + + + + +/** + * @Route("/ogboot/v1/oglives/default", name="setOgliveDefault", methods={"PUT"}) + * @OA\Put( + * path="/ogboot/v1/oglives/default", + * summary="Set default ogLive client", + * @OA\RequestBody( + * required=true, + * @OA\JsonContent( + * type="object", + * @OA\Property( + * property="checksum", + * type="string", + * example="9e49a085ba74f97a81bdf9b3d0785094", + * description="Checksum of the ogLive client to set as default" + * ) + * ) + * ), + * @OA\Response( + * response=200, + * description="ogLive client set as default successfully", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="message", type="string") + * ) + * ), + * @OA\Response( + * response=404, + * description="ogLive client not found" + * ), + * @OA\Response( + * response=500, + * description="Failed to set default ogLive client" + * ) + * ) + */ +public function setOgliveDefault(Request $request): Response +{ + $data = json_decode($request->getContent(), true); + + if (!isset($data['checksum'])) { + return new JsonResponse(['error' => 'Invalid input data'], Response::HTTP_BAD_REQUEST); + } + + $checksum = $data['checksum']; + + // Establecer el ogLive como predeterminado utilizando el checksum proporcionado + $result = $this->curlRequestService->callOgLive("set_default " . escapeshellarg($checksum)); + + if (is_array($result) && isset($result['error'])) { + return new JsonResponse(['error' => $result['error']], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + return new JsonResponse(['message' => 'ogLive client set as default successfully'], Response::HTTP_OK); +} + + + + + + +/** + * @Route("/ogboot/v1/oglives/install", name="installOglive", methods={"POST"}) + * @OA\Post( + * path="/ogboot/v1/oglives/install", + * summary="Install an ogLive client", + * @OA\RequestBody( + * required=true, + * @OA\JsonContent( + * type="object", + * @OA\Property( + * property="isoname", + * type="string", + * example="ogLive-focal-5.13.0-27-beta-amd64-r20210706.5b4bf5f.iso", + * description="Name of the ogLive ISO to install" + * ) + * ) + * ), + * @OA\Response( + * response=200, + * description="ogLive client installed successfully", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="message", type="string"), + * @OA\Property(property="details", type="object") + * ) + * ), + * @OA\Response( + * response=400, + * description="Invalid input data" + * ), + * @OA\Response( + * response=500, + * description="Failed to install ogLive client", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="error", type="string"), + * @OA\Property(property="details", type="string") + * ) + * ) + * ) + */ +public function installOglive(Request $request): Response +{ + $data = json_decode($request->getContent(), true); + + if (!isset($data['isoname'])) { + return new JsonResponse(['error' => 'Invalid input data'], Response::HTTP_BAD_REQUEST); + } + + $isoname = $data['isoname']; + $result = $this->curlRequestService->callOgLive("download " . escapeshellarg($isoname)); + + if (is_array($result) && isset($result['success']) && $result['success'] === false) { + return new JsonResponse(['error' => 'Failed to install ogLive client', 'details' => $result['error']], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + return new JsonResponse(['message' => 'ogLive client installed successfully', 'details' => $result], Response::HTTP_OK); +} + + + +/** + * @Route("/ogboot/v1/oglives/{checksum}", name="uninstallOglive", methods={"DELETE"}) + * @OA\Delete( + * path="/ogboot/v1/oglives/{checksum}", + * summary="Uninstall an ogLive client", + * @OA\Parameter( + * name="checksum", + * in="path", + * description="Checksum of the ogLive client to uninstall", + * required=true, + * @OA\Schema(type="string", example="9e49a085ba74f97a81bdf9b3d0785094") + * ), + * @OA\Response( + * response=200, + * description="ogLive client uninstalled successfully", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="message", type="string"), + * @OA\Property(property="details", type="string") + * ) + * ), + * @OA\Response( + * response=404, + * description="ogLive client not found" + * ), + * @OA\Response( + * response=500, + * description="Failed to uninstall ogLive client", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="error", type="string") + * ) + * ) + * ) + */ +public function uninstallOglive(string $checksum): Response +{ + // Llamada al servicio para desinstalar el cliente ogLive + error_log("Calling curlRequestService with checksum: $checksum"); + $result = $this->curlRequestService->callOgLive("uninstall " . escapeshellarg($checksum)); + + // Verificar la respuesta del servicio + error_log("Service call result: " . print_r($result, true)); + + if (is_array($result) && isset($result['error'])) { + // Error encontrado en la respuesta + error_log("Error found in response: " . $result['error']); + return new JsonResponse(['error' => $result['error']], Response::HTTP_INTERNAL_SERVER_ERROR); + } elseif (is_array($result) && isset($result['message'])) { + // Respuesta exitosa + error_log("Success response: " . print_r($result, true)); + return new JsonResponse([ + 'message' => $result['message'], + 'details' => $result['details'] ?? '' + ], Response::HTTP_OK); + } else { + // Manejar el caso en que no se recibió una respuesta válida del servicio + error_log("Failed to uninstall ogLive client, empty result from service"); + return new JsonResponse(['error' => 'Failed to uninstall ogLive client'], Response::HTTP_INTERNAL_SERVER_ERROR); + } +} + + + + +/** + * @Route("/ogboot/v1/pxes", name="get_boot_files", methods={"GET"}) + * @OA\Get( + * path="/ogboot/v1/pxes", + * summary="Get all PXE boot files", + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent( + * type="object", + * @OA\Property( + * property="boot_files", + * type="array", + * @OA\Items( + * type="string", + * description="Boot file" + * ) + * ) + * ) + * ), + * @OA\Response( + * response=500, + * description="Failed to retrieve boot files" + * ) + * ) + */ +public function getBootFiles(): JsonResponse +{ + + $directory = '/opt/ogboot/tftpboot/ipxe_scripts'; + + + $files = scandir($directory); + + + $bootFiles = array_filter($files, function ($file) use ($directory) { + return !is_dir($directory . '/' . $file) && strpos($file, '01-') === 0; + }); + + $response = [ + 'boot_files' => array_values($bootFiles) + ]; + + return new JsonResponse($response, Response::HTTP_OK); +} + +/** + * @Route("/ogboot/v1/pxes/{mac}", name="ogboot_get_boot_file_with_params", methods={"GET"}) + * @OA\Get( + * path="/ogboot/v1/pxes/{mac}", + * summary="Get boot file with parameters", + * @OA\Parameter( + * name="mac", + * in="path", + * description="MAC address", + * required=true, + * @OA\Schema(type="string", example="00:50:56:22:11:12") + * ), + * @OA\Response( + * response=200, + * description="Successful operation", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="template_name", type="string", description="Template name", example="pxe"), + * @OA\Property(property="mac", type="string", description="MAC address", example="00:50:56:22:11:12"), + * @OA\Property(property="lang", type="string", description="Language", example="es_ES.UTF-8"), + * @OA\Property(property="ip", type="string", description="IP address", example="192.168.2.11"), + * @OA\Property(property="server_ip", type="string", description="Server IP address", example="192.168.2.1"), + * @OA\Property(property="router", type="string", description="Router", example="192.168.2.1"), + * @OA\Property(property="netmask", type="string", description="Netmask", example="255.255.255.0"), + * @OA\Property(property="computer_name", type="string", description="Computer name", example="pc11"), + * @OA\Property(property="netiface", type="string", description="Network interface", example="eth0"), + * @OA\Property(property="group", type="string", description="Group", example="Aula_virtual"), + * @OA\Property(property="ogrepo", type="string", description="Repository IP", example="192.168.2.1"), + * @OA\Property(property="oglive", type="string", description="Live server IP", example="192.168.2.1"), + * @OA\Property(property="oglog", type="string", description="Log server IP", example="192.168.2.1"), + * @OA\Property(property="ogshare", type="string", description="Share server IP", example="192.168.2.1"), + * @OA\Property(property="oglivedir", type="string", description="Live directory", example="ogLive"), + * @OA\Property(property="ogprof", type="string", description="Is professor", example="false"), + * @OA\Property(property="hardprofile", type="string", description="Hardware profile", example=""), + * @OA\Property(property="ogntp", type="string", description="NTP server", example=""), + * @OA\Property(property="ogdns", type="string", description="DNS server", example=""), + * @OA\Property(property="ogproxy", type="string", description="Proxy server", example=""), + * @OA\Property(property="ogunit", type="string", description="Unit directory", example=""), + * @OA\Property(property="resolution", type="string", description="Screen resolution", example="788") + * ) + * ), + * @OA\Response( + * response=500, + * description="Failed to retrieve boot file" + * ) + * ) + */ +public function getBootFile(string $mac): Response +{ + // Ruta donde están alojados los archivos de arranque + $directory = '/opt/ogboot/tftpboot/ipxe_scripts'; + + // Genera el nombre del archivo basado en la dirección MAC + $fileName = "01-" . $mac; + + $filePath = "$directory/$fileName"; + + if (!file_exists($filePath)) { + return new JsonResponse(['error' => 'No se encontró ningún archivo de arranque con la dirección MAC especificada'], Response::HTTP_NOT_FOUND); + } + + $content = file_get_contents($filePath); + $templateName = ''; + $contentLines = explode("\n", $content); + foreach ($contentLines as $line) { + if (strpos($line, '#template_name:') !== false) { + $templateName = trim(str_replace('#template_name:', '', $line)); + break; + } + } + + // Extraer los parámetros del contenido del archivo iPXE + preg_match('/set kernelargs (.*)/', $content, $matches); + if (isset($matches[1])) { + $kernelargs = $matches[1]; + parse_str(str_replace(' ', '&', $kernelargs), $params); + + $result = [ + 'template_name' => $templateName, + 'mac' => $mac, + 'lang' => $params['LANG'] ?? '', + 'ip' => explode(':', $params['ip'])[0] ?? '', + 'server_ip' => explode(':', $params['ip'])[1] ?? '', + 'router' => explode(':', $params['ip'])[2] ?? '', + 'netmask' => explode(':', $params['ip'])[3] ?? '', + 'computer_name' => explode(':', $params['ip'])[4] ?? '', + 'netiface' => explode(':', $params['ip'])[5] ?? '', + 'group' => $params['group'] ?? '', + 'ogrepo' => $params['ogrepo'] ?? '', + 'oglive' => $params['oglive'] ?? '', + 'oglog' => $params['oglog'] ?? '', + 'ogshare' => $params['ogshare'] ?? '', + 'oglivedir' => $params['oglivedir'] ?? '', + 'ogprof' => $params['ogprof'] ?? '', + 'hardprofile' => $params['hardprofile'] ?? '', + 'ogntp' => $params['ogntp'] ?? '', + 'ogdns' => $params['ogdns'] ?? '', + 'ogproxy' => $params['ogproxy'] ?? '', + 'ogunit' => $params['ogunit'] ?? '', + 'resolution' => $params['vga'] ?? '', + ]; + + return new JsonResponse($result, Response::HTTP_OK); + } + + return new JsonResponse(['error' => 'Failed to parse kernel arguments from the boot file'], Response::HTTP_INTERNAL_SERVER_ERROR); +} + + /** + * @Route("/ogboot/v1/pxes", name="create_boot_file", methods={"POST"}) + * @OA\Post( + * path="/ogboot/v1/pxes", + * summary="Create a new PXE boot file", + * @OA\RequestBody( + * required=true, + * @OA\JsonContent( + * type="object", + * @OA\Property(property="template_name", type="string", description="Template name", example="pxe"), + * @OA\Property(property="mac", type="string", description="MAC address", example="00:50:56:22:11:12"), + * @OA\Property(property="lang", type="string", description="Language", example="es_ES.UTF-8"), + * @OA\Property(property="ip", type="string", description="IP address", example="192.168.2.11"), + * @OA\Property(property="server_ip", type="string", description="Server IP address", example="192.168.2.1"), + * @OA\Property(property="router", type="string", description="Router", example="192.168.2.1"), + * @OA\Property(property="netmask", type="string", description="Netmask", example="255.255.255.0"), + * @OA\Property(property="computer_name", type="string", description="Computer name", example="pc11"), + * @OA\Property(property="netiface", type="string", description="Network interface", example="eth0"), + * @OA\Property(property="group", type="string", description="Group", example="Aula_virtual"), + * @OA\Property(property="ogrepo", type="string", description="Repository IP", example="192.168.2.1"), + * @OA\Property(property="oglive", type="string", description="Live server IP", example="192.168.2.1"), + * @OA\Property(property="oglog", type="string", description="Log server IP", example="192.168.2.1"), + * @OA\Property(property="ogshare", type="string", description="Share server IP", example="192.168.2.1"), + * @OA\Property(property="oglivedir", type="string", description="Live directory", example="ogLive"), + * @OA\Property(property="ogprof", type="string", description="Is professor", example="false"), + * @OA\Property(property="hardprofile", type="string", description="Hardware profile", example=""), + * @OA\Property(property="ogntp", type="string", description="NTP server", example=""), + * @OA\Property(property="ogdns", type="string", description="DNS server", example=""), + * @OA\Property(property="ogproxy", type="string", description="Proxy server", example=""), + * @OA\Property(property="ogunit", type="string", description="Unit directory", example=""), + * @OA\Property(property="resolution", type="string", description="Screen resolution", example="788") + * ) + * ), + * @OA\Response( + * response=200, + * description="PXE boot file created successfully", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="message", type="string") + * ) + * ), + * @OA\Response( + * response=400, + * description="Invalid input" + * ), + * @OA\Response( + * response=404, + * description="Template not found" + * ), + * @OA\Response( + * response=500, + * description="Internal server error" + * ) + * ) + */ + public function createBootFile(Request $request): JsonResponse + { + $data = json_decode($request->getContent(), true); + + $templateName = $data['template_name'] ?? null; + $mac = $data['mac'] ?? null; + $serverIp = $data['server_ip'] ?? null; + $ogLiveDir = $data['oglivedir'] ?? 'ogLive'; + + if (!$templateName || !$mac || !$serverIp) { + return new JsonResponse(['error' => 'Invalid input'], Response::HTTP_BAD_REQUEST); + } + + $parameters = [ + 'LANG' => $data['lang'] ?? 'es_ES.UTF-8', + 'ip' => $data['ip'] ?? '', + 'router' => $data['router'] ?? '', + 'netmask' => $data['netmask'] ?? '', + 'computer_name' => $data['computer_name'] ?? '', + 'netiface' => $data['netiface'] ?? '', + 'group' => $data['group'] ?? '', + 'ogrepo' => $data['ogrepo'] ?? '', + 'oglive' => $data['oglive'] ?? $serverIp, + 'oglog' => $data['oglog'] ?? $serverIp, + 'ogshare' => $data['ogshare'] ?? $serverIp, + 'oglivedir' => $data['oglivedir'] ?? '', + 'ogprof' => $data['ogprof'] ?? 'false', + 'hardprofile' => $data['hardprofile'] ?? '', + 'ogntp' => $data['ogntp'] ?? '', + 'ogdns' => $data['ogdns'] ?? '', + 'ogproxy' => $data['ogproxy'] ?? '', + 'ogunit' => $data['ogunit'] ?? '', + 'resolution' => $data['resolution'] ?? '788' + ]; + + $templateDir = '/opt/ogboot/tftpboot/ipxe_scripts/templates'; + $templatePath = $templateDir . '/' . $templateName; + + if (!file_exists($templatePath)) { + return new JsonResponse(['error' => 'Template not found'], Response::HTTP_NOT_FOUND); + } + + $templateContent = file_get_contents($templatePath); + if ($templateContent === false) { + return new JsonResponse(['error' => 'Failed to read template'], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + $kernelArgs = 'ro boot=oginit quiet splash irqpoll acpi=on og2nd=sqfs ogprotocol=smb ogactiveadmin=true ogdebug=true ogtmpfs=15 ' . + 'oglivedir=' . $parameters['oglivedir'] . ' ' . + 'LANG=' . $parameters['LANG'] . ' ' . + 'ip=' . $parameters['ip'] . ':' . $serverIp . ':' . $parameters['router'] . ':' . $parameters['netmask'] . ':' . $parameters['computer_name'] . ':' . $parameters['netiface'] . ':none ' . + 'group=' . str_replace(' ', '_', trim($parameters['group'])) . ' ' . + 'ogrepo=' . $parameters['ogrepo'] . ' ' . + 'oglive=' . $serverIp . ' ' . + 'oglog=' . $serverIp . ' ' . + 'ogshare=' . $serverIp . ' ' . + 'ogprof=' . ($parameters['ogprof'] === 'true' ? 'true' : 'false') . ' ' . + (!empty($parameters['hardprofile']) ? 'hardprofile=' . str_replace(' ', '_', trim($parameters['hardprofile'])) . ' ' : '') . + (!empty($parameters['ogntp']) ? 'ogntp=' . $parameters['ogntp'] . ' ' : '') . + (!empty($parameters['ogdns']) ? 'ogdns=' . $parameters['ogdns'] . ' ' : '') . + (!empty($parameters['ogproxy']) ? 'ogproxy=' . $parameters['ogproxy'] . ' ' : '') . + (!empty($parameters['ogunit']) ? 'ogunit=' . $parameters['ogunit'] . ' ' : '') . + (is_numeric($parameters['resolution']) && $parameters['resolution'] <= 999 ? 'vga=' . $parameters['resolution'] : + (strpos($parameters['resolution'], ':') !== false ? 'video=' . $parameters['resolution'] : ' ' . $parameters['resolution'])); + + + $pxeContent = str_replace(['__INFOHOST__', '__SERVERIP__', '__OGLIVE__'], [$kernelArgs, $serverIp, $ogLiveDir], $templateContent); + + + $pxeFileName = '01-' . $mac; + + $pxeFilePath = '/opt/ogboot/tftpboot/ipxe_scripts/' . $pxeFileName; + + if (file_put_contents($pxeFilePath, $pxeContent) === false) { + return new JsonResponse(['error' => 'Failed to create PXE boot file'], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + return new JsonResponse(['message' => 'PXE boot file created successfully'], Response::HTTP_OK); + } + +/** + * @Route("/ogboot/v1/pxes/{mac}", name="ogboot_delete_boot_file", methods={"DELETE"}) + * @OA\Delete( + * path="/ogboot/v1/pxes/{mac}", + * summary="Eliminar archivo de arranque", + * description="Elimina un archivo de arranque específico utilizando su dirección MAC.", + * @OA\Parameter( + * name="mac", + * in="path", + * description="La dirección MAC del cliente cuyo archivo de arranque se desea eliminar", + * required=true, + * @OA\Schema(type="string", example="00:11:22:33:44:55") + * ), + * @OA\Response( + * response=200, + * description="El archivo de arranque se eliminó correctamente", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="message", type="string", example="El archivo de arranque se eliminó correctamente") + * ) + * ), + * @OA\Response( + * response=404, + * description="No se encontró ningún archivo de arranque con la dirección MAC especificada", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="error", type="string", example="No se encontró ningún archivo de arranque con la dirección MAC especificada") + * ) + * ), + * @OA\Response( + * response=500, + * description="Error al eliminar el archivo de arranque", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="error", type="string", example="Error al eliminar el archivo de arranque") + * ) + * ) + * ) + */ + public function deleteBootFile(string $mac): Response + { + $directory = '/opt/ogboot/tftpboot/ipxe_scripts'; + + $fileName = "01-" . $mac; + + $filePath = "$directory/$fileName"; + + if (!file_exists($filePath)) { + + return new JsonResponse(['error' => 'No se encontró ningún archivo de arranque con la dirección MAC especificada'], Response::HTTP_NOT_FOUND); + } + + if (!unlink($filePath)) { + + return new JsonResponse(['error' => 'Error al eliminar el archivo de arranque'], Response::HTTP_INTERNAL_SERVER_ERROR); + } + return new JsonResponse(['message' => 'El archivo de arranque se eliminó correctamente'], Response::HTTP_OK); + } + + + /** + * @Route("/ogboot/v1/pxe-templates", methods={"GET"}) + * @OA\Get( + * path="/ogboot/v1/pxe-templates", + * summary="Obtener todas las plantillas PXE", + * description="Obtiene una lista de todas las plantillas de arranque PXE disponibles.", + * @OA\Response( + * response=200, + * description="Lista de plantillas de arranque PXE", + * @OA\JsonContent( + * type="object", + * @OA\Property( + * property="templates", + * type="array", + * @OA\Items(type="string", example="mi_plantilla.ipxe") + * ) + * ) + * ), + * @OA\Response( + * response=500, + * description="Error interno del servidor" + * ) + * ) + */ + public function getAllTemplates(): JsonResponse + { + $templateDir = '/opt/ogboot/tftpboot/ipxe_scripts/templates'; + + // Verificar si el directorio existe + if (!is_dir($templateDir)) { + return new JsonResponse(['error' => 'Directorio de plantillas no encontrado'], Response::HTTP_INTERNAL_SERVER_ERROR); + } + + // Obtener la lista de archivos en el directorio + $files = scandir($templateDir); + + // Filtrar los archivos válidos (excluir directorios y archivos ocultos) + $templates = array_filter($files, function ($file) use ($templateDir) { + return !in_array($file, ['.', '..']) && !is_dir($templateDir . '/' . $file); + }); + + return new JsonResponse(['templates' => array_values($templates)], Response::HTTP_OK); + } + + /** + * @Route("/ogboot/v1/pxe-templates/{templateName}", name="get_template", methods={"GET"}) + * @OA\Get( + * path="/ogboot/v1/pxe-templates/{templateName}", + * summary="Obtener contenido de una plantilla PXE", + * description="Obtiene el contenido de una plantilla de arranque PXE específica por su nombre.", + * @OA\Parameter( + * name="templateName", + * in="path", + * required=true, + * description="Nombre de la plantilla PXE", + * @OA\Schema(type="string", example="mi_plantilla.ipxe") + * ), + * @OA\Response( + * response=200, + * description="Contenido de la plantilla de arranque PXE", + * @OA\MediaType( + * mediaType="text/plain", + * @OA\Schema(type="string", example="#!ipxe\nset timeout 0\nset timeout-style hidden\n\nset ISODIR ogLive\nset default 0\nset kernelargs INFOHOST\nkernel tftp://SERVERIP/ogLive/ogvmlinuz ${kernelargs}\ninitrd tftp://SERVERIP/ogLive/oginitrd.img\nboot") + * ) + * ), + * @OA\Response( + * response=404, + * description="Plantilla no encontrada" + * ), + * @OA\Response( + * response=500, + * description="Error interno del servidor" + * ) + * ) + */ + public function getTemplate(string $templateName): Response + { + $templateDir = '/opt/ogboot/tftpboot/ipxe_scripts/templates'; + $filePath = "$templateDir/$templateName"; + + if (!file_exists($filePath)) { + return new Response(null, Response::HTTP_NOT_FOUND); + } + + $content = file_get_contents($filePath); + if ($content === false) { + return new Response('Error al leer la plantilla', Response::HTTP_INTERNAL_SERVER_ERROR); + } + + return new Response($content, Response::HTTP_OK, ['Content-Type' => 'text/plain']); + } + + /** + * @Route("/ogboot/v1/pxe-templates", methods={"POST"}) + * + * @OA\Post( + * path="/ogboot/v1/pxe-templates", + * summary="Crear Plantilla", + * description="Crea una nueva plantilla de arranque utilizando los datos proporcionados.", + * @OA\RequestBody( + * required=true, + * @OA\JsonContent( + * type="object", + * @OA\Property(property="name_template", type="string", example="pxe"), + * @OA\Property(property="content_template", type="string", example="#!ipxe\nset timeout 0\nset timeout-style hidden\n\nset ISODIR __OGLIVE__\nset default 0\nset kernelargs __INFOHOST__\n# Menú de entrada para seleccionar OgLive\n:try_iso\nkernel tftp://__SERVERIP__/${ISODIR}/ogvmlinuz ${kernelargs} || goto fallback\ninitrd tftp://__SERVERIP__/${ISODIR}/oginitrd.img\nboot\n\n:fallback\necho \"OgLive default\"\nset ISODIR ogLive\nkernel tftp://__SERVERIP__/${ISODIR}/ogvmlinuz ${kernelargs}\ninitrd tftp://__SERVERIP__/${ISODIR}/oginitrd.img\nboot") + * ) + * ), + * @OA\Response( + * response=200, + * description="La plantilla de arranque se creó exitosamente.", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="message", type="string", example="Plantilla creada exitosamente."), + * @OA\Property(property="template", type="string", example="#!ipxe\nset timeout 0\nset timeout-style hidden\n\nset ISODIR __OGLIVE__\nset default 0\nset kernelargs __INFOHOST__\n# Menú de entrada para seleccionar OgLive\n:try_iso\nkernel tftp://__SERVERIP__/${ISODIR}/ogvmlinuz ${kernelargs} || goto fallback\ninitrd tftp://__SERVERIP__/${ISODIR}/oginitrd.img\nboot\n\n:fallback\necho \"OgLive default\"\nset ISODIR ogLive\nkernel tftp://__SERVERIP__/${ISODIR}/ogvmlinuz ${kernelargs}\ninitrd tftp://__SERVERIP__/${ISODIR}/oginitrd.img\nboot") + * ) + * ), + * @OA\Response( + * response=400, + * description="La solicitud no pudo ser procesada debido a un error en los datos proporcionados en el cuerpo de la solicitud." + * ), + * @OA\Response( + * response=500, + * description="Ocurrió un error al crear la plantilla de arranque." + * ) + * ) + */ + public function createTemplate(Request $request) + { + $data = json_decode($request->getContent(), true); + + if (!isset($data['name_template']) || !isset($data['content_template'])) { + return new Response('La solicitud no pudo ser procesada debido a un error en los datos proporcionados en el cuerpo de la solicitud.', 400); + } + + $nameTemplate = $data['name_template']; + $contentTemplate = $data['content_template']; + $templateDir = '/opt/ogboot/tftpboot/ipxe_scripts/templates'; + if (!preg_match('/^[a-zA-Z0-9._-]+$/', $nameTemplate)) { + return new Response('Nombre de la plantilla no válido.', 400); + } + + $filePath = $templateDir . '/' . $nameTemplate; + $contentTemplate = str_replace("\\n", "\n", $contentTemplate); + try { + file_put_contents($filePath, $contentTemplate); + } catch (\Exception $e) { + return new Response('Ocurrió un error al crear la plantilla de arranque. ' . $e->getMessage(), 500); + } + + return new Response(json_encode([ + 'message' => 'Plantilla creada exitosamente.', + 'template' => $contentTemplate + ]), 200, ['Content-Type' => 'application/json']); + } + + + + /** + * @Route("/ogboot/v1/pxe-templates/{name}", methods={"DELETE"}) + * + * @OA\Delete( + * path="/ogboot/v1/pxe-templates/{name}", + * summary="Eliminar Plantilla", + * description="Elimina una plantilla de arranque específica utilizando su nombre.", + * @OA\Parameter( + * name="name", + * in="path", + * required=true, + * description="El nombre de la plantilla de arranque que se desea eliminar.", + * @OA\Schema(type="string") + * ), + * @OA\Response( + * response=200, + * description="La plantilla de arranque se eliminó correctamente.", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="message", type="string", example="Plantilla eliminada correctamente.") + * ) + * ), + * @OA\Response( + * response=404, + * description="No se encontró ninguna plantilla de arranque con el nombre especificado." + * ), + * @OA\Response( + * response=500, + * description="Ocurrió un error al eliminar la plantilla de arranque." + * ) + * ) + */ + public function deleteTemplate($name) + { + $templateDir = '/opt/ogboot/tftpboot/ipxe_scripts/templates'; + $filePath = $templateDir . '/' . $name; + + if (!file_exists($filePath)) { + return new Response('No se encontró ninguna plantilla de arranque con el nombre especificado.', 404); + } + + try { + unlink($filePath); + } catch (\Exception $e) { + return new Response('Ocurrió un error al eliminar la plantilla de arranque.', 500); + } + + return new Response(json_encode([ + 'message' => 'Plantilla eliminada correctamente.' + ]), 200, ['Content-Type' => 'application/json']); + } + + +} diff --git a/src/OgBootBundle/OgBootBundle.php b/src/OgBootBundle/OgBootBundle.php new file mode 100644 index 0000000..4a2e467 --- /dev/null +++ b/src/OgBootBundle/OgBootBundle.php @@ -0,0 +1,10 @@ + false, 'output' => 'Error al crear el socket']; + } + + $result = socket_connect($socket, $socketPath); + if ($result === false) { + syslog(LOG_ERR, 'Error al conectar con el socket: ' . socket_strerror(socket_last_error($socket))); + socket_close($socket); + return ['success' => false, 'output' => 'Error al conectar con el socket']; + } + + $command = [ + 'action' => 'download', + 'args' => [$isoname] + ]; + + socket_write($socket, json_encode($command), strlen(json_encode($command))); + + $response = ''; + $status = []; + + while ($buffer = socket_read($socket, 2048)) { + $response .= $buffer; + $status[] = json_decode($buffer, true); + } + + socket_close($socket); + + // Analiza el último estado recibido + $lastStatus = end($status); + if ($lastStatus && $lastStatus['status'] === 'completed') { + return ['success' => true, 'output' => $lastStatus]; + } else { + return ['success' => false, 'output' => $status]; + } +} + + + +public function callOgLive($parameter) +{ + $socketPath = '/var/run/oglive/oglive_daemon.sock'; + + file_put_contents('/tmp/serviceOglive.log', 'callOgLive called with parameter: ' . $parameter . PHP_EOL, FILE_APPEND); + + $socket = socket_create(AF_UNIX, SOCK_STREAM, 0); + if ($socket === false) { + $error = 'Error al crear el socket: ' . socket_strerror(socket_last_error()); + file_put_contents('/tmp/serviceOglive.log', 'Socket creation error: ' . $error . PHP_EOL, FILE_APPEND); + return [ + 'success' => false, + 'error' => $error + ]; + } + + $result = socket_connect($socket, $socketPath); + if ($result === false) { + $error = 'Error al conectar con el socket: ' . socket_strerror(socket_last_error($socket)); + file_put_contents('/tmp/serviceOglive.log', 'Socket connection error: ' . $error . PHP_EOL, FILE_APPEND); + socket_close($socket); + return [ + 'success' => false, + 'error' => $error + ]; + } + + $args = array_map('trim', explode(' ', $parameter)); + $action = array_shift($args); + $command = [ + 'action' => $action, + 'args' => $args + ]; + + socket_write($socket, json_encode($command), strlen(json_encode($command))); + + $response = ''; + while ($buffer = socket_read($socket, 2048)) { + $response .= $buffer; + } + + socket_close($socket); + + file_put_contents('/tmp/serviceOglive.log', 'Raw response: ' . $response . PHP_EOL, FILE_APPEND); + + if (empty($response)) { + $error = 'Respuesta vacía del demonio'; + file_put_contents('/tmp/serviceOglive.log', 'Empty response error: ' . $error . PHP_EOL, FILE_APPEND); + return [ + 'success' => false, + 'error' => $error + ]; + } + + $decodedResponse = json_decode($response, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + $error = 'Error al decodificar JSON: ' . json_last_error_msg(); + file_put_contents('/tmp/serviceOglive.log', 'JSON decode error: ' . $error . PHP_EOL, FILE_APPEND); + return [ + 'success' => false, + 'error' => $error + ]; + } + + if (isset($decodedResponse['success']) && $decodedResponse['success']) { + file_put_contents('/tmp/serviceOglive.log', 'Decoded successful response: ' . json_encode($decodedResponse['output']) . PHP_EOL, FILE_APPEND); + return $decodedResponse['output']; + } else { + $error = $decodedResponse['error'] ?? 'Unknown error'; + file_put_contents('/tmp/serviceOglive.log', 'Error in response: ' . $error . PHP_EOL, FILE_APPEND); + return [ + 'success' => false, + 'error' => $error + ]; + } +} + + + + +} diff --git a/src/Repository/.gitignore b/src/Repository/.gitignore new file mode 100644 index 0000000..e69de29