Compare commits

..

84 Commits

Author SHA1 Message Date
Nicolas Arenas 0e4024353e Updated default admin user for ogcore 2025-05-14 09:17:06 +02:00
Nicolas Arenas 6983bba294 Updted installer 2025-05-14 07:00:27 +02:00
Nicolas Arenas 561232ee9f Check selected components in forms 2025-05-13 16:30:31 +02:00
Nicolas Arenas 3f9265cf69 Improve log output for installer 2025-05-07 17:43:59 +02:00
Nicolas Arenas 6217bc71eb Add debug.log to the installer
oginstaller/pipeline/head There was a failure building this commit Details
2025-04-14 13:13:59 +02:00
Nicolas Arenas 324d47823a Add debug.log to the installer
oginstaller/pipeline/head There was a failure building this commit Details
2025-04-14 13:07:17 +02:00
Nicolas Arenas 7afae7db6d Add nightly support
oginstaller/pipeline/head There was a failure building this commit Details
2025-04-14 12:44:17 +02:00
root 59367aacaa Added nightly repos
oginstaller/pipeline/head There was a failure building this commit Details
2025-04-10 16:45:43 +00:00
Nicolas Arenas a038748ff5 Updated installer script
oginstaller/pipeline/head There was a failure building this commit Details
2025-04-03 09:36:49 +02:00
Nicolas Arenas 65a086064b Update oginstaller to create release file
oginstaller/pipeline/head There was a failure building this commit Details
2025-04-03 09:24:57 +02:00
Nicolas Arenas 8110058303 Add release file and first version of updater
oginstaller/pipeline/head There was a failure building this commit Details
2025-04-02 11:27:58 +02:00
Nicolas Arenas 30506275af Add release file and first version of updater 2025-04-02 11:26:37 +02:00
Nicolas Arenas 4789cddbfc Update installer to handle dev and prod repos
oginstaller/pipeline/head There was a failure building this commit Details
2025-03-28 14:26:11 +01:00
Nicolas Arenas 1a54a31fe0 Updated installer
oginstaller/pipeline/head There was a failure building this commit Details
2025-03-27 12:19:59 +01:00
Nicolas Arenas ee6c7c2003 oginstaller get oglives list from server or return default value 2025-03-22 12:20:11 +01:00
Nicolas Arenas 36b8ee0250 Updated version for installer 2025-03-21 19:23:41 +01:00
Nicolas Arenas 92e24117e0 First installer version for debian packages 2025-03-21 18:48:13 +01:00
Nicolas Arenas 45938bbe2b Adds https port to mercure connections
oginstaller/pipeline/head There was a failure building this commit Details
2025-03-17 13:23:26 +01:00
Nicolas Arenas 4ca2252f2d Fix typo
oginstaller/pipeline/head There was a failure building this commit Details
2025-03-13 14:01:16 +01:00
Nicolas Arenas 148a78f408 Updated ogcoreURL var
oginstaller/pipeline/head There was a failure building this commit Details
2025-03-13 13:49:36 +01:00
Nicolas Arenas d4f0aed18d Add mercure to old installer
oginstaller/pipeline/head There was a failure building this commit Details
2025-03-13 13:46:06 +01:00
Nicolas Arenas 9151520b93 Update ogLive version
oginstaller/pipeline/head There was a failure building this commit Details
2025-03-12 12:31:54 +01:00
Nicolas Arenas 5d056feaf1 Updated ogrepository files
oginstaller/pipeline/head There was a failure building this commit Details
2025-03-12 10:01:35 +01:00
Nicolas Arenas 3d355ec4a4 Updates ogcore provision wth menu creation, roles and comands
oginstaller/pipeline/head There was a failure building this commit Details
2025-02-07 10:54:21 +01:00
Nicolas Arenas 3b2b5d8702 Merge pull request 'ogrepo-keys' (#2) from ogrepo-keys into main
oginstaller/pipeline/head There was a failure building this commit Details
Reviewed-on: #2
2025-02-07 09:38:35 +01:00
Nicolas Arenas 8a634a5f71 Create opngsys user by installer
oginstaller/pipeline/head There was a failure building this commit Details
oginstaller/pipeline/pr-main There was a failure building this commit Details
Set permissions to env.json
2025-02-06 14:18:41 +01:00
Nicolas Arenas b717e2b7ee Modifying ssh keys
oginstaller/pipeline/head There was a failure building this commit Details
2025-02-06 12:52:00 +01:00
Nicolas Arenas c38fe4e26f Adjust paths for oginstaller
oginstaller/pipeline/head There was a failure building this commit Details
2025-02-06 12:44:47 +01:00
Nicolas Arenas 3344157d6b Deploy ssh keys to repo
oginstaller/pipeline/head There was a failure building this commit Details
2025-02-06 12:22:09 +01:00
Nicolas Arenas 22650060af Ensure download repo to exist 2025-02-06 12:11:41 +01:00
Nicolas Arenas 4634061f41 Fix env.json owner 82 82 to allow changes in container 2025-02-06 12:09:46 +01:00
Nicolas Arenas 20a338a0d6 Fixing env.json permissions 2025-02-06 12:05:43 +01:00
Nicolas Arenas 3fe3fc9a5c Modifying Files
oginstaller/pipeline/head There was a failure building this commit Details
2025-02-06 11:59:13 +01:00
Nicolas Arenas 105340b165 Add ssh keys
oginstaller/pipeline/head There was a failure building this commit Details
2025-02-06 11:54:23 +01:00
Nicolas Arenas b652142aaa Reestructura de los directorios
oginstaller/pipeline/head There was a failure building this commit Details
2025-02-06 11:53:34 +01:00
Nicolas Arenas 625c464ff5 Merge pull request 'working-installer' (#1) from working-installer into main
oginstaller/pipeline/head There was a failure building this commit Details
Reviewed-on: #1
2025-01-09 15:34:01 +01:00
Nicolas Arenas a39258f978 Replace oglive for latest
oginstaller/pipeline/head There was a failure building this commit Details
oginstaller/pipeline/pr-main Build started... Details
2024-12-05 16:34:19 +01:00
Nicolas Arenas 8e235f526d Update default oglive version
oginstaller/pipeline/head This commit looks good Details
2024-12-04 13:03:48 +01:00
Nicolas Arenas 00a96b0a84 Hanfle interfaces exception for json
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-22 10:05:21 +01:00
Nicolas Arenas ee28a98157 fix ogcore parameter for iprepository
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-21 01:19:05 +01:00
Nicolas Arenas 69bdd32f74 Fixing max eight
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-21 00:30:38 +01:00
Nicolas Arenas 0096a5b942 Fix typo
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-21 00:29:28 +01:00
Nicolas Arenas 8f08e35f42 Modified default oglive
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-21 00:25:06 +01:00
Nicolas Arenas 5ef82a4d4f Adjust dhcp directory parameter name
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 22:55:08 +01:00
Nicolas Arenas f6fe1615f4 deactivate python venv after questions
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 22:12:44 +01:00
Nicolas Arenas b8f53f91e8 Finishing provision if user ogadmin
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 21:46:00 +01:00
Nicolas Arenas 77053c4142 Recover CONF_DIR variable
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 21:36:46 +01:00
Nicolas Arenas cbf1f0715d Add default value for password
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 21:33:24 +01:00
Nicolas Arenas 3fbbb3b340 Leave deploy yaml to docker
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 21:27:29 +01:00
Nicolas Arenas 30ae35c6f1 Add cat to view file
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 21:23:46 +01:00
Nicolas Arenas 9e7cc4e22d Create missing directory
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 21:11:42 +01:00
Nicolas Arenas 858763c473 Improve tag selection
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 20:13:19 +01:00
Nicolas Arenas 237ccf6675 Add default values for password
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 18:29:43 +01:00
Nicolas Arenas b7c3523b10 Update parameters for ogrepository 2024-11-20 18:29:26 +01:00
Nicolas Arenas 033ecb17e7 Fix typo in directory
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 14:09:00 +01:00
Nicolas Arenas 176b0cba2e Adjust release parameter
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 14:00:50 +01:00
Nicolas Arenas 3b00609c1e Clean oginstaller
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-20 13:49:43 +01:00
Nicolas Arenas 0a0bd59f8d Install pip dependencies 2024-11-20 13:49:05 +01:00
Nicolas Arenas 4916d15736 Create version to use npyscreen module 2024-11-20 13:44:11 +01:00
Nicolas Arenas 412334bc23 Fix typo in Tieout start
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-15 13:43:41 +01:00
Nicolas Arenas 82effbb989 Fix a couple of typos in messages and defaults
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 18:09:25 +01:00
Nicolas Arenas 3f49623417 Making timeout start seconds higher
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 18:04:28 +01:00
Nicolas Arenas c5257913c0 Fix proble with ogrepository
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 12:10:25 +01:00
Nicolas Arenas 6acebd38a4 Update path for ogrepository provision
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 10:56:44 +01:00
Nicolas Arenas bb379b7c68 Fix repo path
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 09:56:34 +01:00
Nicolas Arenas 1b6535ffee Fix directory Get OGCORE BRANCH for components
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 09:22:49 +01:00
Nicolas Arenas e53fc605bb Mejorar la respuesta del script a diferentes eventos
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 09:02:20 +01:00
Nicolas Arenas 278564d35f shorting oglive image
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 08:58:08 +01:00
Nicolas Arenas a1cf2b59dd Calling rest of installers
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 08:41:09 +01:00
Nicolas Arenas b2305740b2 Improving input methods
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 07:26:50 +01:00
Nicolas Arenas bc024b08c1 Fix provision gui problem
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 02:20:28 +01:00
Nicolas Arenas 9450a61467 Fix ogrepository URL
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 01:45:25 +01:00
Nicolas Arenas 23c28e4534 Fixing minor prooblems
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 01:38:42 +01:00
Nicolas Arenas b40fc19e55 Create function to launch component installer
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 01:22:25 +01:00
Nicolas Arenas 12ca1e75fd oginstaller launch installer
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-14 01:18:27 +01:00
Nicolas Arenas 520b2861fc Component installer downloads specific release
- Python script gets tags from repo
2024-11-14 01:17:30 +01:00
Nicolas Arenas 52e1c2f5a8 Updating installer paths
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-13 21:50:31 +01:00
Nicolas Arenas 1fbbdb657c make oginstall executable
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-13 21:45:33 +01:00
Nicolas Arenas d6d2df9855 Update CONFIG DIR Variable for systemd unit
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-13 21:21:37 +01:00
Nicolas Arenas 50f2c60d31 Enable debug in installer
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-13 21:11:22 +01:00
Nicolas Arenas dda794f328 Fix path for installer
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-13 20:57:15 +01:00
Nicolas Arenas a09814c113 Changes config dirs to current direcotry
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-13 20:55:10 +01:00
Nicolas Arenas 5a7952efe3 push comp
oginstaller/pipeline/head There was a failure building this commit Details
2024-11-13 20:48:17 +01:00
Nicolas Arenas 8162f5b3c3 Merge branch 'move-to-docker'
oginstaller/pipeline/head This commit looks good Details
2024-11-05 01:12:43 +01:00
63 changed files with 2622 additions and 143 deletions

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -27,7 +27,6 @@ then
debootstrap --arch=amd64 --variant=$VARIANT $DIST $CHROOT_DIR $DEBOOT_STRAP_URL
fi
mkdir -p $CHROOT_DIR/usr/share/calamares/branding/
mkdir -p $CHROOT_DIR/etc/calamares/modules/
cp calamares/settings.conf $CHROOT_DIR/etc/calamares/
@ -88,8 +87,6 @@ chmod +x $UBUNTU_CHROOT_DIR/opengnsys-installer/provision_oggui.sh
cp pat.txt $UBUNTU_CHROOT_DIR/opengnsys-installer/
# Setup the chroot for ubuntu
mount --bind /dev/ $UBUNTU_CHROOT_DIR/dev
mount --bind /run/ $UBUNTU_CHROOT_DIR/run

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -84,8 +84,8 @@ insmod all_video
set default="0"
set timeout=30
menuentry "Install Opengnsys" {
linux /casper/vmlinuz boot=casper ipv6.disable=1 quiet splash ---
menuentry "Install Ubuntu FS" {
linux /casper/vmlinuz boot=casper quiet splash ---
initrd /casper/initrd
}

View File

@ -16,31 +16,10 @@ if [ -f /opt/opengnsys/ogCore/installer/.deployed ]; then
exit 0
fi
CONF_DIR=/opt/opengnsys/ogCore/etc/
containers="ogcore-database ogcore-php gcore-nginx"
while true ; do
all_running=true
for container in $containers; do
if ! docker compose -f $CONF_DIR/docker-compose-deploy.yml ps --format json |jq -r '"\(.Name) \(.State)"' |grep -q "$container running"
then
echo "El contenedor $container no está corriendo"
all_running=false
fi
done
if $all_running; then
echo "Todos los contenedores están corriendo"
break
fi
sleep 5
while ! docker compose -f $CONF_DIR/docker-compose-deploy.yml ps --format json |jq -r '"\(.Name) \(.State)"' |grep -q 'ogcore-php running'; do
sleep 2
done
# while ! docker compose -f $CONF_DIR/docker-compose-deploy.yml ps --format json |jq -r '"\(.Name) \(.State)"' |grep -q 'ogcore-php running'; do
# sleep 2
# done
sleep 5
adminuser=$(jq -r '.username' /opt/opengnsys/ogCore/installer/config.json)
adminpass=$(jq -r '.password' /opt/opengnsys/ogCore/installer/config.json)
@ -57,13 +36,12 @@ bearer=$(curl -k -X 'POST' 'https://localhost:8443/auth/login' -H 'accept: a
if [ $adminuser == "ogadmin" ]; then
echo "Cambiando password a ogadmin no puede ser el usuario administrador"
ogadmin_uuid=$(curl -f -q -k -L https://localhost:8443/users/?username=ogadmin -H 'accept: application/json' -H "Authorization: Bearer $bearer" | jq .[0].uuid | sed 's/"//g')
curl -f -k -L -X PUT "https://localhost:8443/users/$ogadmin_uuid/reset-password" -H 'accept: application/ld+json' -H 'Content-Type: application/ld+json' -d "{\"currentPassword\": \"12345678\", \"newPassword\": \"$adminpass\", \"repeatNewPassword\": \"$adminpass\"}" -H "Authorization: Bearer $bearer"
touch /opt/opengnsys/ogCore/installer/.deployed
ogadmin_uuid=$(curl -q -k -L https://localhost:8443/users/?username=ogadmin -H 'accept: application/json' -H "Authorization: Bearer $bearer" | jq .[0].uuid | sed 's/"//g')
curl -k -L -X PUT "https://localhost:8443/users/$ogadmin_uuid/reset-password" -H 'accept: application/ld+json' -H 'Content-Type: application/ld+json' -d "{\"currentPassword\": \"12345678\", \"newPassword\": \"$adminpass\", \"repeatNewPassword\": \"$adminpass\"}" -H "Authorization: Bearer $bearer"
exit 0
fi
curl -k -L -f --location 'https://localhost:8443/users' \
curl -k -L --location 'https://localhost:8443/users' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $bearer" \
--data "{ \"username\": \"$adminuser\", \"password\": \"$adminpass\", \"roles\": [\"ROLE_SUPER_ADMIN\"] }"

View File

@ -8,10 +8,10 @@ ENV_FILE=$ENV_DIR/.env
mkdir -p $ENV_DIR
# Comprobar si ya se ha instalado ogCore
if [ -f /opt/opengnsys/ogGui/installer/.deployed ]; then
echo "ogCore ya instalado"
exit 0
fi
#if [ -f /opt/opengnsys/ogGui/installer/.deployed ]; then
# echo "ogCore ya instalado"
# exit 0
#fi
# Sacar la IP del ogCore de la configuración
ogcore_ip=$(jq -r '.ogcore_ip' /opt/opengnsys/ogGui/installer/config.json)
@ -28,7 +28,6 @@ if [ -z "$ogcore_ip" ]; then
ogcore_ip=$ip_address
# Si no se ha configurado la IP del ogCore, se escribe en el fichero .env
echo "NG_APP_BASE_API_URL=https://$ogcore_ip:8443" > $ENV_FILE
touch /opt/opengnsys/ogGui/installer/.deployed
exit 0
else
echo "No se pudo determinar el interfaz asociado a la ruta por defecto."

View File

@ -30,7 +30,6 @@ WorkingDirectory=/opt/opengnsys/ogCore/repo/
ExecStart=/usr/bin/docker compose -f /opt/opengnsys/ogCore/etc/docker-compose-deploy.yml up
ExecStartPost=/opengnsys-installer/provision_ogcore.sh
ExecStop=/usr/bin/docker compose -f /opt/opengnsys/ogCore/etc/docker-compose-deploy.yml stop
TimeoutSec=600
Restart=always
[Install]
@ -83,7 +82,7 @@ OGBOOT_REPO="$OPENGNSYS_BASE_URL/ogboot.git"
OGCORE_REPO="$OPENGNSYS_BASE_URL/ogcore.git"
OGDHCP_REPO="$OPENGNSYS_BASE_URL/ogdhcp.git"
OGGUI_REPO="$OPENGNSYS_BASE_URL/oggui.git"
OGREPOSITORY_REPO="$OPENGNSYS_BASE_URL/ogrepo.git"
OGREPOSITORY_REPO="$OPENGNSYS_BASE_URL/ogrepository.git"
export GIT_SSL_NO_VERIFY=1
echo ======================================== > /etc/issue

View File

@ -0,0 +1,198 @@
#!/usr/bin/bash
set -x
# Paso 1: Seleccionar los componentes
# Los componentes a instalar se encuentran en el directorio /tmp/opengnsys-installer-configs
# Set configuration
COMPONENTS="ogCore ogGui ogDhcp ogBoot ogRepository"
CONFIGS_DIR=/tmp/oginstall
# PAT_FILE=/opengnsys-installer/pat.txt
# PAT=$(cat $PAT_FILE | tr -d '\n\r\t')
# OPENGNSYS_BASE_URL="https://$PAT@ognproject.evlt.uma.es/gitea/opengnsys"ls
OPENGNSYS_BASE_URL="https://ognproject.evlt.uma.es/gitea/opengnsys"
OGBOOT_REPO="$OPENGNSYS_BASE_URL/ogboot.git"
OGCORE_REPO="$OPENGNSYS_BASE_URL/ogcore.git"
OGDHCP_REPO="$OPENGNSYS_BASE_URL/ogdhcp.git"
OGGUI_REPO="$OPENGNSYS_BASE_URL/oggui.git"
OGREPOSITORY_REPO="$OPENGNSYS_BASE_URL/ogrepository.git"
export GIT_SSL_NO_VERIFY=1
# Creamos el usuario opengnsys
useradd -m -d /opt/opengnsys -r -s /bin/bash opengnsys
## Functions
function install_docker() {
apt-get -y update
apt-get -y install ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
tee /etc/apt/sources.list.d/docker.list > /dev/null
apt-get -y update
apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
systemctl enable docker
}
function install_ogcore_docker() {
cat <<EOF > /etc/systemd/system/ogcore.service
[Unit]
Description=Servicio para ejecutar Docker Compose de ogCore
After=docker.service
Requires=docker.service
[Service]
WorkingDirectory=/opt/opengnsys/ogCore/repo/
ExecStart=/usr/bin/docker compose -f /opt/opengnsys/ogCore/etc/docker-compose-deploy.yml up
ExecStartPost=/opt/opengnsys/ogCore/bin/provision_ogcore.sh
ExecStop=/usr/bin/docker compose -f /opt/opengnsys/ogCore/etc/docker-compose-deploy.yml stop
Restart=always
TimeoutStartSec=600
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now ogcore
}
function install_oggui_docker() {
# Sacar la IP del ogCore de la configuración
oggui_version=$(jq -r '.release' /opt/opengnsys/ogGui/installer/config.json)
# Exportar los valores como variables de entorno
ENV_DIR=/opt/opengnsys/ogGui/etc/
ENV_FILE=$ENV_DIR/.env
cat <<EOF > /etc/systemd/system/oggui-app.service
[Unit]
Description=Servicio para contenedor Docker de OgGui
After=docker.service
Requires=docker.service
[Service]
Restart=always
ExecStartPre=/opt/opengnsys/ogGui/bin/provision_oggui.sh
ExecStart=/usr/bin/docker run --rm --name ogGui-app -p 4200:4200 -v $ENV_FILE:/app/.env opengnsys/oggui:$oggui_version
ExecStop=/usr/bin/docker stop ogGui-app
TimeoutStartSec=600
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now oggui-app
}
function git_checkout_release() {
git clone --no-checkout "$1" "$2"
cd "$2" || exit
git checkout tags/"$3"
cd - || exit
}
echo ======================================== > /etc/issue
echo "OpenGnSys Installer" >> /etc/issue
echo "Componentes instalados:" >> /etc/issue
for component in $COMPONENTS
do
config_file="config_${component}.json"
if [ -f $CONFIGS_DIR/$config_file ]; then
echo "Componente $component seleccionado, instalando configuración..."
component_dir=/opt/opengnsys/$component
mkdir -p $component_dir/installer
mkdir -p $component_dir/repo
cp $CONFIGS_DIR/$config_file /opt/opengnsys/$component/installer/config.json
case $component in
ogCore)
echo "Instalando ogCore..."
OGCORE_BRANCH=$(jq -r '.release' /opt/opengnsys/ogCore/installer/config.json)
container_version=$(jq -r '.release' /opt/opengnsys/ogCore/installer/config.json)
git_checkout_release "$OGCORE_REPO" "$component_dir/repo" "$OGCORE_BRANCH"
mkdir -p $component_dir/etc
cp $component_dir/repo/.env $component_dir/etc/
cp $component_dir/repo/env.json $component_dir/etc/
chown 82:82 $component_dir/etc/env.json
mkdir -p $component_dir/bin/
cp $CONFIGS_DIR/provision_ogcore.sh $component_dir/bin/
chmod 755 $component_dir/bin/provision_ogcore.sh
cp $component_dir/repo/docker-compose-deploy.yml $component_dir/etc/
sed -i "s/static/$container_version/g" $component_dir/etc/docker-compose-deploy.yml
cat $component_dir/etc/docker-compose-deploy.yml
echo - ogCore >> /etc/issue
install_docker
install_ogcore_docker
;;
ogGui)
echo "Instalando ogGui..."
OGGUI_BRANCH=$(jq -r '.container_version' /opt/opengnsys/ogGui/installer/config.json)
mkdir -p $component_dir/bin
cp $CONFIGS_DIR/provision_oggui.sh $component_dir/bin/
chmod 755 $component_dir/bin/provision_oggui.sh
git_checkout_release "$OGGUI_REPO" "$component_dir/repo" "$OGGUI_BRANCH"
echo - ogGui >> /etc/issue
install_docker
install_oggui_docker
;;
ogDhcp)
echo "Instalando ogDhcp..."
OGCORE_BRANCH=$(jq -r '.release' /opt/opengnsys/ogDhcp/installer/config.json)
git_checkout_release "$OGDHCP_REPO" "$component_dir/repo" "$OGCORE_BRANCH"
cp $CONFIGS_DIR/$config_file $component_dir/repo/installer/config_ogdhcp.json
cd $component_dir/repo/installer || exit
chmod 755 ogdhcp_installer.sh
./ogdhcp_installer.sh
cd - || exit
echo - ogDhcp >> /etc/issue
;;
ogBoot)
OGCORE_BRANCH=$(jq -r '.release' /opt/opengnsys/ogBoot/installer/config.json)
echo "Instalando ogBoot..."
git_checkout_release "$OGBOOT_REPO" "$component_dir/repo" "$OGCORE_BRANCH"
cp $CONFIGS_DIR/$config_file $component_dir/repo/installer/config.json
apt install -y python3 git vim
cd $component_dir/repo/installer || exit
python3 ogboot_installer.py
cd - || exit
echo - ogBoot >> /etc/issue
;;
ogRepository)
echo "Instalando ogRepository..."
OGCORE_BRANCH=$(jq -r '.release' /opt/opengnsys/ogRepository/installer/config.json)
git_checkout_release "$OGREPOSITORY_REPO" "$component_dir/repo" "$OGCORE_BRANCH"
cp $CONFIGS_DIR/$config_file $component_dir/installer/config.json
REPO_IP=$(jq -r '.ogrepository_ip' $component_dir/installer/config.json)
CORE_IP=$(jq -r '.ogcore_server_ip' $component_dir/installer/config.json)
OGUSER=$(jq -r '.ogrepository_samba_user' $component_dir/installer/config.json)
OGPASS=$(jq -r '.ogrepository_samba_pass' $component_dir/installer/config.json)
mkdir -p $component_dir/bin
cp $CONFIGS_DIR/provision_ogrepository.sh $component_dir/bin/
chmod 755 $component_dir/bin/provision_ogrepository.sh
$component_dir/bin/provision_ogrepository.sh $REPO_IP $CORE_IP $OGUSER $OGPASS $component_dir/repo
echo - ogRepository >> /etc/issue
;;
*)
echo "Componente $component no reconocido"
;;
esac
continue
fi
done
echo ======================================== >> /etc/issue
# rm -f $PAT_FILE

View File

@ -0,0 +1,56 @@
#!/bin/bash
#
set -x
CONF_DIR=/opt/opengnsys/ogCore/etc/
cd /opt/opengnsys/ogCore/repo/ || exit
if [ -f /opt/opengnsys/ogCore/installer/.deployed ]; then
echo "ogCore ya instalado"
exit 0
fi
while ! docker compose -f $CONF_DIR/docker-compose-deploy.yml ps --format json |jq -r '"\(.Name) \(.State)"' |grep -q 'ogcore-php running'; do
sleep 2
done
adminuser=$(jq -r '.username' /opt/opengnsys/ogCore/installer/config.json)
adminpass=$(jq -r '.password' /opt/opengnsys/ogCore/installer/config.json)
# Despliega la aplicación
docker compose -f $CONF_DIR/docker-compose-deploy.yml exec php composer install
# Genera las claves de JWT
docker compose -f $CONF_DIR/docker-compose-deploy.yml exec php php bin/console lexik:jwt:generate-keypair --overwrite
# Crea/actualiza la base de datos
docker compose -f $CONF_DIR/docker-compose-deploy.yml exec php php bin/console doctrine:migrations:migrate --no-interaction
# Carga los datos por defecto: usuario
docker compose -f $CONF_DIR/docker-compose-deploy.yml exec php php bin/console doctrine:fixtures:load --no-interaction
# Carga los datos por defecto: roles
docker compose -f $CONF_DIR/docker-compose-deploy.yml exec php bin/console app:load-default-user-groups
# Carga los datos por defecto: comandos
docker compose -f $CONF_DIR/docker-compose-deploy.yml exec php php bin/console app:load-default-commands
# Carga los datos por defecto: menú
docker compose -f $CONF_DIR/docker-compose-deploy.yml exec php php bin/console opengnsys:load-default-menu
# Provision user admin
bearer=$(curl -k -X 'POST' 'https://localhost:8443/auth/login' -H 'accept: application/json' -H 'Content-Type: application/json' -d "{ \"username\": \"ogadmin\", \"password\": \"12345678\" }" | jq .token | sed 's/"//g' )
if [ $adminuser == "ogadmin" ]; then
echo "Cambiando password a ogadmin no puede ser el usuario administrador"
ogadmin_uuid=$(curl -q -k -L https://localhost:8443/users/?username=ogadmin -H 'accept: application/json' -H "Authorization: Bearer $bearer" | jq .[0].uuid | sed 's/"//g')
curl -k -L -X PUT "https://localhost:8443/users/$ogadmin_uuid/reset-password" -H 'accept: application/ld+json' -H 'Content-Type: application/ld+json' -d "{\"currentPassword\": \"12345678\", \"newPassword\": \"$adminpass\", \"repeatNewPassword\": \"$adminpass\"}" -H "Authorization: Bearer $bearer"
touch /opt/opengnsys/ogCore/installer/.deployed
exit 0
fi
curl -k -L --location 'https://localhost:8443/users' \
--header 'Content-Type: application/json' \
--header "Authorization: Bearer $bearer" \
--data "{ \"username\": \"$adminuser\", \"password\": \"$adminpass\", \"roles\": [\"ROLE_SUPER_ADMIN\"] }"
touch /opt/opengnsys/ogCore/installer/.deployed
exit 0

View File

@ -0,0 +1,27 @@
#!/bin/bash
#
set -x
# preparar el fichero .env
ENV_DIR=/opt/opengnsys/ogGui/etc/
ENV_FILE=$ENV_DIR/.env
mkdir -p $ENV_DIR
# Comprobar si ya se ha instalado ogCore
if [ -f /opt/opengnsys/ogGui/installer/.deployed ]; then
echo "ogCore ya instalado"
exit 0
fi
# Sacar la IP del ogCore de la configuración
ogcore_url=$(jq -r '.ogcore_ip' /opt/opengnsys/ogGui/installer/config.json)
mercure_ip=$(jq -r '.mercure_ip' /opt/opengnsys/ogGui/installer/config.json)
export OGCORE_URL="$ogcore_url"
export MERCURE_URL="$mercure_url"
# Si se ha configurado la IP del ogCore, se escribe en el fichero .env
echo "NG_APP_BASE_API_URL=$OGCORE_URL" > $ENV_FILE
echo "NG_APP_OGCORE_MERCURE_BASE_URL=https://$mercure_ip:3000/.well-known/mercure" >> $ENV_FILE
touch /opt/opengnsys/ogGui/installer/.deployed

View File

@ -0,0 +1,113 @@
#!/bin/bash
set -e
REPO_IP=${1:-"127.0.0.1"}
CORE_IP=${2:-"127.0.0.1"}
OGUSER=${3:-"opengnsys"}
OGPASS=${4:-"og"}
INSTALL_DIR=/opt/opengnsys/ogrepository
DOWNLOAD_DIR=${5:-"/tmp/ogrepository"}
DEBIAN_FRONTEND=noninteractive
OGUSER_HOME=/opt/opengnsys
export DEBIAN_FRONTEND
export GIT_SSL_NO_VERIFY
check_root() {
if [ "$(id -u)" != "0" ]; then
echo "This script must be run as root" 1>&2
exit 1
fi
}
install_uftp() {
apt install uftp -y
systemctl stop uftp
systemctl disable uftp
}
install_updcast () {
apt install $DOWNLOAD_DIR/packets/udpcast_20230924_amd64.deb
}
add_user_ogrepository() {
if ! id "$OGUSER" &>/dev/null; then
echo "User ogrepository does not exist, creating it"
useradd -m -d $OGUSER_HOME -r -s /bin/bash $OGUSER
fi
if [ ! -d $OGUSER_HOME/.ssh ] ; then
mkdir -p $OGUSER_HOME/.ssh
cp /tmp/oginstall/ssh-keys/opengnsys $OGUSER_HOME/.ssh/id_ed25519
cp /tmp/oginstall/ssh-keys/opengnsys.pub $OGUSER_HOME/.ssh/id_ed25519.pub
cat /tmp/oginstall/ssh-keys/opengnsys.pub >> $OGUSER_HOME/.ssh/authorized_keys
chown -R $OGUSER:$OGUSER $OGUSER_HOME/.ssh
chmod 0600 $OGUSER_HOME/.ssh/*
fi
if [ ! -f /etc/sudoers.d/$OGUSER ]; then
echo "User $OGUSER does not have sudo permissions, adding it"
echo "$OGUSER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/"$OGUSER"
fi
}
create_directories() {
mkdir -p $INSTALL_DIR
mkdir -p $INSTALL_DIR/images $INSTALL_DIR/images_trash/ $INSTALL_DIR/bin/ $INSTALL_DIR/etc/ $INSTALL_DIR/log/ $INSTALL_DIR/api/ $INSTALL_DIR/images_virtual
chown -R $OGUSER:$OGUSER $INSTALL_DIR
}
install_dependencies() {
apt update -y
apt install -y git python3 python3-pip python3-flask python3-paramiko python3-psutil python3-flasgger debian-archive-keyring samba gunicorn wakeonlan lzop partclone qemu-utils
}
install_ext_repo() {
cp $DOWNLOAD_DIR/installer/files/ctorrent.sources /etc/apt/sources.list.d/ctorrent.sources
apt update -y
}
install_external_packages() {
apt install -y bittorrent bittornado ctorrent
}
install_ogrepo-api_service() {
cp -r $DOWNLOAD_DIR/installer/files/ogrepo-api.service /etc/systemd/system/ogrepo-api.service
sed -i "s/%%OGREPOSITORY_USER%%/$OGUSER/g" /etc/systemd/system/ogrepo-api.service
systemctl enable --now ogrepo-api
}
install_files() {
cp -pr $DOWNLOAD_DIR/bin/* $INSTALL_DIR/bin/
cp -pr $DOWNLOAD_DIR/etc/* $INSTALL_DIR/etc/
cp -pr $DOWNLOAD_DIR/api/* $INSTALL_DIR/api/
chown -R $OGUSER:$OGUSER $INSTALL_DIR
chmod 755 $INSTALL_DIR/bin/*
echo IPlocal="$REPO_IP" > $INSTALL_DIR/etc/ogAdmRepo.cfg
echo IPcore="$CORE_IP" >> $INSTALL_DIR/etc/ogAdmRepo.cfg
sudo chown $OGUSER:$OGUSER $INSTALL_DIR/etc/ogAdmRepo.cfg
}
configure_samba() {
echo "include = /etc/samba/smb.conf.ogrepository" >> /etc/samba/smb.conf
cp $DOWNLOAD_DIR/installer/files/ogrepo-smb.conf /etc/samba/smb.conf.ogrepository
sed -i "s/%%OGREPOSITORY_USER%%/$OGUSER/g" /etc/samba/smb.conf.ogrepository
systemctl restart smbd
# Create default user ogrepository
(echo $OGPASS; echo $OGPASS) | smbpasswd -s -a $OGUSER
}
## Main program
install_dependencies
add_user_ogrepository
install_ext_repo
install_external_packages
install_uftp
install_updcast
create_directories
install_files
install_ogrepo-api_service
configure_samba

View File

@ -0,0 +1,7 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACBs7Wbqztq5ixPGFL+1DlTa0T6QUBMiLq6KxZnCJ5rofQAAAJD7Xj89+14/
PQAAAAtzc2gtZWQyNTUxOQAAACBs7Wbqztq5ixPGFL+1DlTa0T6QUBMiLq6KxZnCJ5rofQ
AAAEC4UmYDisgl5jNR6SUwRA80k6Qc06cBHg1mW3+2NU6SfmztZurO2rmLE8YUv7UOVNrR
PpBQEyIurorFmcInmuh9AAAABm5vbmFtZQECAwQFBgc=
-----END OPENSSH PRIVATE KEY-----

View File

@ -0,0 +1 @@
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGztZurO2rmLE8YUv7UOVNrRPpBQEyIurorFmcInmuh9 noname

View File

@ -0,0 +1,54 @@
import os
from git import Repo
from packaging.version import Version
def get_highest_remote_tag(repo_path):
try:
# Abre el repositorio local
repo = Repo(repo_path)
# Asegúrate de que el repositorio tiene un remoto
if not repo.remotes:
return None # No hay remotos configurados
# Obtén el remoto por defecto (origin o el primero disponible)
remote = repo.remotes.origin
# Recupera los tags remotos
remote.fetch(tags=True)
remote_tags = [ref.name.split('/')[-1] for ref in repo.references if ref.path.startswith('refs/tags/')]
if not remote_tags:
return None # No hay tags remotos
# Ordena los tags remotos por versión
tags_sorted = sorted(remote_tags, key=lambda t: Version(t) if t.replace('.', '').isdigit() else Version('0.0.0'), reverse=True)
return tags_sorted[0] if tags_sorted else None
except Exception as e:
print(f"Error al procesar el repositorio {repo_path}: {e}")
return None
def process_selected_repositories(base_path, repo_names):
repo_highest_tags = {}
for repo_name in repo_names:
repo_path = os.path.join(base_path, repo_name)
if os.path.exists(repo_path) and os.path.isdir(os.path.join(repo_path, '.git')):
highest_tag = get_highest_remote_tag(repo_path)
repo_highest_tags[repo_name] = highest_tag
else:
repo_highest_tags[repo_name] = "No es un repositorio Git válido"
return repo_highest_tags
# Ruta base donde están los repositorios locales
base_path = "../"
# Lista de nombres de repositorios específicos
repo_names = [ "ogcore" , "oggui" , "ogboot" , "ogdhcp" , "ogrepository" ]
result = process_selected_repositories(base_path, repo_names)
# Muestra los resultados
for repo_name, tag in result.items():
print(f'{repo_name}:{tag}')

View File

@ -0,0 +1,217 @@
import npyscreen
import json
import os
from git import Repo
CONFIGS_DIR = "/tmp/oginstall"
os.makedirs(CONFIGS_DIR, exist_ok=True)
REPO_URL = "https://ognproject.evlt.uma.es/gitea/opengnsys/ogcore.git"
def get_git_tags():
try:
repo_path = os.path.join(CONFIGS_DIR, "opengnsys_repo")
if not os.path.exists(repo_path):
print("Clonando el repositorio...")
Repo.clone_from(REPO_URL, repo_path)
else:
print("Usando repositorio existente en", repo_path)
repo = Repo(repo_path)
tags = [tag.name for tag in repo.tags if tag.name.startswith("opengnsys")]
return tags
except Exception as e:
print("Error al obtener los tags:", str(e))
return []
class ComponentSelectionForm(npyscreen.ActionForm):
def create(self):
self.components = self.add(npyscreen.TitleMultiSelect, max_height=6, name="Selecciona los componentes",
values=["ogCore", "ogGui", "ogDhcp", "ogBoot", "ogRepository"], scroll_exit=True)
self.tags = get_git_tags()
self.tag = self.add(npyscreen.TitleSelectOne, max_height=10, name="Selecciona el tag",
values=self.tags, scroll_exit=True)
def beforeEditing(self):
npyscreen.blank_terminal()
def on_ok(self):
npyscreen.blank_terminal()
selected_components = [self.components.values[i] for i in self.components.value]
if not selected_components or not self.tag.value:
npyscreen.notify_confirm("Debes seleccionar al menos un componente y un tag.", title="Error")
return
selected_tag = self.tags[self.tag.value[0]]
self.parentApp.selected_components = selected_components
self.parentApp.selected_tag = selected_tag
self.parentApp.current_component_index = 0
self.parentApp.switchForm(selected_components[0])
def on_cancel(self):
if npyscreen.notify_yes_no("¿Estás seguro de que deseas salir?", title="Confirmación"):
self.parentApp.setNextForm(None)
class ComponentForm(npyscreen.ActionForm):
component_name = None
def create(self):
self.fields = {}
def beforeEditing(self):
npyscreen.blank_terminal()
self.fields.clear()
self._recreate_form()
def _recreate_form(self):
"""Limpia y recrea los widgets del formulario."""
self._clear_widgets()
self.configure_fields()
def configure_fields(self):
"""Método para definir los campos de configuración para cada componente"""
pass
def _clear_widgets(self):
"""Limpia todos los widgets del formulario."""
self._widgets__ = []
self._widgets_by_id__ = {}
self._contained_widgets = []
def validate_fields(self):
"""Validaciones personalizadas para contraseñas."""
password_field = None
confirmation_field = None
# Identificar los campos de contraseña y confirmación
for key, field_data in self.fields.items():
if field_data.get("is_password_field"):
password_field = field_data["widget"]
if field_data.get("is_password_confirmation"):
confirmation_field = field_data["widget"]
# Validar contraseñas si ambos campos están definidos
if password_field and confirmation_field:
if password_field.value != confirmation_field.value:
npyscreen.notify_confirm("Las contraseñas no coinciden. Por favor, revísalas.", title="Error")
return False
return True
def add_password_field(self, key, name, is_confirmation=False, default_value=""):
"""Añade un campo de contraseña con metadatos."""
widget = self.add(npyscreen.TitlePassword, name=name, value=default_value)
self.fields[key] = {
"widget": widget,
"is_password_field": not is_confirmation,
"is_password_confirmation": is_confirmation,
}
def on_ok(self):
if not self.validate_fields():
return # Si las validaciones fallan, no proceder
npyscreen.blank_terminal()
config_data = {"release": self.parentApp.selected_tag}
for key, field_data in self.fields.items():
config_data[key] = field_data["widget"].value
config_file = os.path.join(CONFIGS_DIR, f"config_{self.component_name}.json")
with open(config_file, "w") as f:
json.dump(config_data, f)
npyscreen.notify_confirm(f"Configuración de {self.component_name} guardada en {config_file}", title="Confirmación")
self.parentApp.current_component_index += 1
if self.parentApp.current_component_index < len(self.parentApp.selected_components):
next_component = self.parentApp.selected_components[self.parentApp.current_component_index]
self.parentApp.switchForm(next_component)
else:
self.parentApp.setNextForm(None)
def on_cancel(self):
if npyscreen.notify_yes_no("¿Estás seguro de que deseas salir?", title="Confirmación"):
self.parentApp.setNextForm(None)
class OgCoreForm(ComponentForm):
component_name = "ogCore"
def configure_fields(self):
self.fields["username"] = {"widget": self.add(npyscreen.TitleText, name="Usuario administrador (ogadmin):", value="ogadmin")}
self.add_password_field("password", "Contraseña:" , default_value="12345678")
self.add_password_field("confirm_password", "Confirmar Contraseña:", is_confirmation=True, default_value="12345678")
class OgGuiForm(ComponentForm):
component_name = "ogGui"
def configure_fields(self):
self.fields["ogcore_ip"] = {"widget": self.add(npyscreen.TitleText, name="URL Api OgCore (https://127.0.0.1:8443):", value="https://127.0.0.1:8443")}
self.fields["mercure_ip"] = {"widget": self.add(npyscreen.TitleText, name="Mercue IP (127.0.0.1):", value="127.0.0.1")}
class OgDhcpForm(ComponentForm):
component_name = "ogDhcp"
def configure_fields(self):
self.fields["ogbootIP"] = {"widget": self.add(npyscreen.TitleText, name="IP servidor de Boot (127.0.0.1):", value="127.0.0.1")}
self.fields["ogDhcpIP"] = {"widget": self.add(npyscreen.TitleText, name="IP servidor de DHCP (127.0.0.1):", value="127.0.0.1")}
self.fields["ogDhcp_Dir"] = {"widget": self.add(npyscreen.TitleText, name="Directorio de ogdhcp (/opt/opengnsys/ogdhcp):", value="/opt/opengnsys/ogdhcp")}
self.fields["interfaces"] = {"widget": self.add(npyscreen.TitleText, name="Interfaces Boot (eth0,eth1):", value="eth0,eth1")}
def on_ok(self):
if not self.validate_fields():
return # Si las validaciones fallan, no proceder
npyscreen.blank_terminal()
config_data = {"release": self.parentApp.selected_tag}
for key, field_data in self.fields.items():
if key == "interfaces":
config_data[key] = [iface.strip() for iface in field_data["widget"].value.split(",")]
else:
config_data[key] = field_data["widget"].value
config_file = os.path.join(CONFIGS_DIR, f"config_{self.component_name}.json")
with open(config_file, "w") as f:
json.dump(config_data, f)
npyscreen.notify_confirm(f"Configuración de {self.component_name} guardada en {config_file}", title="Confirmación")
self.parentApp.current_component_index += 1
if self.parentApp.current_component_index < len(self.parentApp.selected_components):
next_component = self.parentApp.selected_components[self.parentApp.current_component_index]
self.parentApp.switchForm(next_component)
else:
self.parentApp.setNextForm(None)
class OgBootForm(ComponentForm):
component_name = "ogBoot"
def configure_fields(self):
self.fields["ogCore_ServerIP"] = {"widget": self.add(npyscreen.TitleText, name="ogCore IP:", value="")}
self.fields["ogBoot_ServerIP"] = {"widget": self.add(npyscreen.TitleText, name="ogBoot Server IP:", value="")}
self.fields["ogBoot_Dir"] = {"widget": self.add(npyscreen.TitleText, name="ogCore Dir (/opt/opengnsys/ogboot):", value="/opt/opengnsys/ogboot")}
self.fields["ogLive_Default"] = {"widget": self.add(npyscreen.TitleText, name="ogLive por defecto:", value="https://ognproject.evlt.uma.es/oglive/ogLive-noble-6.8.0-31-generic-amd64-r20250116.538e3fa_20250120.iso")}
self.fields["ogBootSambaUser"] = {"widget": self.add(npyscreen.TitleText, name="ogBoot Samba User (opengnsys):", value="opengnsys")}
self.add_password_field("ogBootSambaPass", "ogBoot Samba Pass (og):", default_value="og")
self.add_password_field("confirm_ogBootSambaPass", "Confirmar ogBoot Samba Pass (og):", is_confirmation=True, default_value="og")
class OgRepositoryForm(ComponentForm):
component_name = "ogRepository"
def configure_fields(self):
self.fields["ogrepository_ip"] = {"widget": self.add(npyscreen.TitleText, name="ogRepository IP:", value="127.0.0.1")}
self.fields["ogcore_server_ip"] = {"widget": self.add(npyscreen.TitleText, name="ogCoreserver IP(127.0.0.1):", value="127.0.0.1")}
self.fields["ogrepository_samba_user"] = {"widget": self.add(npyscreen.TitleText, name="Samba User:", value="opengnsys")}
self.add_password_field("ogrepository_samba_pass", "Samba Password:", default_value="og")
self.add_password_field("confirm_repository_password", "Confirmar Samba Password:", is_confirmation=True, default_value="og")
class ConfigApp(npyscreen.NPSAppManaged):
def onStart(self):
self.addForm("MAIN", ComponentSelectionForm, name="Selección de Componentes")
self.addForm("ogCore", OgCoreForm, name="Configuración de ogCore")
self.addForm("ogGui", OgGuiForm, name="Configuración de ogGui")
self.addForm("ogDhcp", OgDhcpForm, name="Configuración de ogDhcp")
self.addForm("ogBoot", OgBootForm, name="Configuración de ogBoot")
self.addForm("ogRepository", OgRepositoryForm, name="Configuración de ogRepository")
self.selected_components = []
self.selected_tag = ""
self.current_component_index = 0
if __name__ == "__main__":
app = ConfigApp()
app.run()

View File

@ -0,0 +1,989 @@
import npyscreen
import os
from git import Repo
import subprocess # Importar el módulo subprocess
import requests # Importar el módulo requests
import time # Importar time para simular el progreso
import threading # Importar threading para leer el log en tiempo real
import socket
import sys # Importar sys para leer los argumentos del script
import logging # Importar el módulo logging
import shutil # Importar para verificar el tamaño del terminal
CONFIGS_DIR = "/tmp/oginstall"
LOGS_DIR = "/var/log/oginstaller"
REPO_URL = "https://ognproject.evlt.uma.es/gitea/opengnsys/ogcore.git"
# Configurar logging y directorio configuración
try:
if not os.path.exists(LOGS_DIR):
os.makedirs(LOGS_DIR, exist_ok=True)
LOG_FILE = os.path.join(LOGS_DIR, "oginstall.log")
if not os.path.exists(CONFIGS_DIR):
os.makedirs(CONFIGS_DIR, exist_ok=True)
logging.basicConfig(
filename=LOG_FILE,
level=logging.DEBUG,
format="%(asctime)s - %(levelname)s - %(message)s",
)
logging.debug("Inicio del programa") # Mensaje inicial para verificar que el log se crea
except Exception as e:
print(f"[ERROR] No se pudo configurar el logging: {e}")
print(f"[ERROR] Verifica los permisos del directorio: {LOGS_DIR}")
exit(1) # Salir si no se puede configurar el logging
def get_network_interfaces():
"""Obtiene los nombres de las interfaces de red disponibles en el servidor."""
try:
# Listar las interfaces de red desde /sys/class/net
interfaces = os.listdir('/sys/class/net')
# Filtrar interfaces válidas (excluyendo interfaces virtuales como 'lo')
valid_interfaces = [iface for iface in interfaces if not iface.startswith('lo')]
return ','.join(valid_interfaces) # Devuelve las interfaces separadas por comas
except Exception as e:
# En caso de error, devolver un valor por defecto
print(f"Error al obtener las interfaces de red: {e}")
return "eth0" # Valor por defecto
def get_available_versions():
"""Obtiene la lista de versiones desde el archivo JSON remoto."""
try:
# Validar si se pasa un argumento
if len(sys.argv) > 1:
arg = sys.argv[1].strip().lower()
logging.debug(f"Argumento recibido: {arg}") # Usar logging en lugar de print
# Validar explícitamente los valores permitidos
if arg == "devel":
url = "https://ognproject.evlt.uma.es/debian-opengnsys/versions-dev.json"
elif arg == "nightly":
logging.debug("No hay versiones disponibles para nightly. Usando 'latest'.")
return ["latest"] # Devolver solo la opción 'latest'
else:
logging.debug(f"Argumento no reconocido: {arg}. Usando versiones de producción.")
url = "https://ognproject.evlt.uma.es/debian-opengnsys/versions-prod.json"
else:
logging.debug("No se pasó ningún argumento. Usando versiones de producción.")
url = "https://ognproject.evlt.uma.es/debian-opengnsys/versions-prod.json"
# Realizar la solicitud HTTP
logging.debug(f"Realizando solicitud HTTP a: {url}")
response = requests.get(url, timeout=10)
response.raise_for_status() # Lanza una excepción si la respuesta no es 200 OK
# Registrar el contenido de la respuesta para depuración
logging.debug(f"Contenido de la respuesta: {response.text}")
# Intentar analizar el JSON
try:
data = response.json()
except ValueError as e:
logging.error(f"Error al analizar el JSON: {e}")
raise RuntimeError(f"El contenido de la respuesta no es un JSON válido: {e}")
# Validar que el JSON contiene la clave "versions"
versions = data.get("versions", [])
if not versions:
logging.warning("La lista de versiones está vacía.")
logging.debug(f"Versiones obtenidas: {versions}")
return versions
except (requests.RequestException, ValueError, RuntimeError) as e:
logging.error(f"No se pudo obtener la lista de versiones: {e}")
raise RuntimeError(f"Error crítico: {e}") # Lanzar una excepción crítica
def get_default_ip():
"""Obtiene la IP asociada a la interfaz por defecto del servidor."""
logging.debug("Obteniendo la IP por defecto del servidor.")
try:
# Obtener la interfaz asociada a la ruta por defecto
result = subprocess.run(
["ip", "route", "get", "1.1.1.1"],
capture_output=True,
text=True,
check=True
)
# Extraer la interfaz de la salida
interface = next((line.split()[-1] for line in result.stdout.splitlines() if "dev" in line), None)
if not interface:
logging.error("No se pudo determinar la interfaz por defecto.")
raise ValueError("No se pudo determinar la interfaz por defecto.")
logging.debug(f"Interfaz por defecto: {interface}")
# Obtener la IP de la interfaz
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.connect(("8.8.8.8", 80))
ip_address = s.getsockname()[0]
return ip_address
except Exception as e:
logging.error(f"Error al obtener la IP por defecto: {e}")
return "192.168.2.2" # Valor por defecto
def get_oglive_list():
"""Obtiene la lista de valores de oglives desde la URL."""
try:
# Realizar la solicitud HTTP
response = requests.get("https://ognproject.evlt.uma.es/oglive/", timeout=10)
response.raise_for_status() # Lanza una excepción si la respuesta no es 200 OK
# Registrar el contenido de la respuesta para depuración
# Extraer los enlaces del contenido HTML
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.text, "html.parser")
links = [a["href"] for a in soup.find_all("a", href=True) if "ogLive" in a["href"]]
# Ordenar los enlaces por la parte después del guion bajo
sorted_links = sorted(links, key=lambda x: x.split("_")[1] if "_" in x else x, reverse=True)
return sorted_links
except Exception as e:
logging.error(f"Error al obtener la lista de oglives: {e}")
return [] # Devolver una lista vacía en caso de error
# Variable global para la IP por defecto
DEFAULT_IP = get_default_ip()
class InstallationTypeForm(npyscreen.ActionForm):
"""Formulario para seleccionar el tipo de instalación."""
def create(self):
self.installation_type = self.add(
npyscreen.TitleSelectOne,
name="Selecciona el tipo de instalación:",
values=["Mononodo", "Multinodo"],
scroll_exit=False, max_height=10
)
def on_ok(self):
"""Guardar la selección y pasar al formulario correspondiente."""
logging.debug(f"Entrando en InstallationTypeForm")
if self.installation_type.value is None:
npyscreen.notify_confirm("Debes seleccionar un tipo de instalación.", title="Error")
return
if self.installation_type.value == [0]: # Mononodo
logging.debug("Instalación mononodo seleccionada.")
self.parentApp.installation_type = "mononodo"
self.parentApp.setNextForm("MONONODO_CONFIG")
elif self.installation_type.value == [1]: # Multinodo
logging.debug("Instalación multinodo seleccionada.")
self.parentApp.installation_type = "multinodo"
self.parentApp.setNextForm("MULTINODO_CONFIG")
def on_cancel(self):
"""Salir de la aplicación."""
if npyscreen.notify_yes_no("¿Estás seguro de que deseas salir?", title="Confirmación"):
self.parentApp.setNextForm(None)
class MononodoConfigForm(npyscreen.ActionForm):
"""Formulario para configurar Mononodo."""
def create(self):
self.server_ip = self.add(
npyscreen.TitleText,
name="IP del servidor (mononodo):",
value=get_default_ip()
)
def on_ok(self):
"""Guardar la configuración y pasar al siguiente formulario."""
logging.debug(f"Entrando en MononodoConfigForm")
self.parentApp.server_ip = self.server_ip.value
self.parentApp.setNextForm("MAIN")
def on_cancel(self):
"""Volver al formulario de selección de tipo de instalación."""
self.parentApp.setNextForm("INSTALLATION_TYPE")
class MultinodoConfigForm(npyscreen.ActionForm):
"""Formulario para configurar Multinodo."""
def create(self):
self.repo_ip = self.add(
npyscreen.TitleText,
name="IP del servidor Repository:",
value=get_default_ip()
)
self.dhcp_ip = self.add(
npyscreen.TitleText,
name="IP del servidor DHCP:",
value=get_default_ip()
)
self.core_ip = self.add(
npyscreen.TitleText,
name="IP del servidor Core:",
value=get_default_ip()
)
self.boot_ip = self.add(
npyscreen.TitleText,
name="IP del servidor Boot:",
value=get_default_ip()
)
def on_ok(self):
"""Guardar la configuración y pasar al siguiente formulario."""
self.parentApp.repo_ip = self.repo_ip.value
self.parentApp.dhcp_ip = self.dhcp_ip.value
self.parentApp.core_ip = self.core_ip.value
self.parentApp.boot_ip = self.boot_ip.value
self.parentApp.setNextForm("MAIN")
def on_cancel(self):
"""Volver al formulario de selección de tipo de instalación."""
self.parentApp.setNextForm("INSTALLATION_TYPE")
class ComponentSelectionForm(npyscreen.ActionForm):
def create(self):
self.components = self.add(
npyscreen.TitleMultiSelect,
max_height=6,
name="Selecciona los componentes",
values=["ogCore", "ogGui", "ogDhcp", "ogBoot", "ogRepository"],
scroll_exit=True
)
self.versions = get_available_versions() # Obtener las versiones desde el archivo JSON
# Si no hay versiones disponibles, usar "latest" como opción por defecto
if not self.versions:
self.versions = ["latest"]
self.tag = self.add(
npyscreen.TitleSelectOne,
max_height=10,
name="Selecciona la versión",
values=self.versions,
scroll_exit=True
)
self.tag.value = [0] # Marcar "latest" (o la primera opción) por defecto
# Mostrar la IP del servidor si es mononodo
if self.parentApp.installation_type == "mononodo":
self.server_ip = self.add(
npyscreen.TitleText,
name="IP del servidor (mononodo):",
value=self.parentApp.server_ip,
editable=False
)
# Agregar un cuadro de texto para mostrar el log
self.log_box = self.add(
npyscreen.BoxTitle,
name="Log de depuración",
max_height=10,
scroll_exit=True
)
def beforeEditing(self):
"""Configurar los valores iniciales de los componentes según el tipo de instalación."""
if self.parentApp.installation_type == "mononodo":
# Seleccionar todos los componentes por defecto
self.components.value = list(range(len(self.components.values)))
else:
# No seleccionar ningún componente por defecto
self.components.value = []
self.display()
def on_ok(self):
# Validar selección obligatoria de componentes y versión
if not self.components.value or len(self.components.value) == 0:
npyscreen.notify_confirm("Debes seleccionar al menos un componente.", title="Error")
return
if not self.tag.value or len(self.tag.value) == 0:
npyscreen.notify_confirm("Debes seleccionar una versión.", title="Error")
return
if self.parentApp.installation_type == "mononodo":
self.handle_mononodo()
else:
self.handle_multinodo()
def handle_mononodo(self):
npyscreen.blank_terminal()
selected_components = [self.components.values[i].lower() for i in self.components.value]
selected_tag = self.versions[self.tag.value[0]]
self.parentApp.selected_components = selected_components
self.parentApp.selected_tag = selected_tag
self.parentApp.current_component_index = 0
self.parentApp.configurations = {}
self.parentApp.switchForm(selected_components[0])
def handle_multinodo(self):
selected_components = [self.components.values[i].lower() for i in self.components.value]
selected_tag = self.versions[self.tag.value[0]]
self.parentApp.selected_components = selected_components
self.parentApp.selected_tag = selected_tag
self.parentApp.current_component_index = 0
self.parentApp.configurations = {}
self.parentApp.switchForm(selected_components[0])
class ComponentForm(npyscreen.ActionForm):
component_name = None
def create(self):
# Agregar un título dinámico basado en el componente en la primera línea
self.title = self.add(
npyscreen.FixedText,
value="",
editable=False,
color="STANDOUT",
rely=0 # Forzar que el título esté en la primera línea
)
self.fields = {}
def beforeEditing(self):
npyscreen.blank_terminal()
# Actualizar el valor del título dinámico basado en el componente
self.title.value = f"Configuración del componente: {self.component_name.upper()}"
self.title.display()
self._recreate_form()
def _recreate_form(self):
"""Limpia y recrea los widgets del formulario, excepto el título."""
# No eliminar el título al recrear los widgets
self._widgets__ = [self.title]
self._widgets_by_id__ = {id(self.title): self.title}
self._contained_widgets = [self.title]
self.configure_fields()
def configure_fields(self):
"""Método para definir los campos de configuración para cada componente"""
pass
def on_ok(self):
npyscreen.blank_terminal()
component_config = {}
for key, field_data in self.fields.items():
component_config[key] = field_data["widget"].value
self.parentApp.configurations[self.component_name] = component_config
self.parentApp.current_component_index += 1
if self.parentApp.current_component_index < len(self.parentApp.selected_components):
next_component = self.parentApp.selected_components[self.parentApp.current_component_index]
self.parentApp.switchForm(next_component)
else:
self.parentApp.generate_debconf()
self.parentApp.setNextForm(None)
def on_cancel(self):
if npyscreen.notify_yes_no("¿Estás seguro de que deseas salir?", title="Confirmación"):
self.parentApp.setNextForm(None)
class OgCoreForm(ComponentForm):
component_name = "ogcore"
def configure_fields(self):
self.add(npyscreen.FixedText, value="Usuario Administrador: ", editable=False, rely=2, relx=2, color="SAFE" , highlighted=True)
self.fields["adminUser"] = {
"widget": self.add(
npyscreen.Textfield,
value="ogadmin",
rely=3, # Línea siguiente
relx=18,
highlighted=True
)
}
self.fields["adminPass"] = {
"widget": self.add(
npyscreen.TitlePassword,
name="Contraseña Administrador:",
value="12345678",
rely=6 , # Ajustar la posición vertical
highlighted=True ,
scroll_exit=True
)
}
class OgGuiForm(ComponentForm):
component_name = "oggui"
def configure_fields(self):
"""Configura los campos del formulario según el tipo de instalación."""
if self.parentApp.installation_type == "mononodo":
self.server_ip = self.parentApp.server_ip
elif self.parentApp.installation_type == "multinodo":
self.server_ip = self.parentApp.core_ip
self.add(npyscreen.FixedText, value="URL del servidor Core:", editable=False, rely=2, relx=2, color="SAFE" , highlighted=True)
self.fields["ogcoreUrl"] = {
"widget": self.add(
npyscreen.Textfield,
value=f"https://{self.server_ip}:8443",
rely=3 ,
relx=18,
highlighted=True# Ajustar la posición vertical
)
}
self.add(npyscreen.FixedText, value="URL del servidor Mercure:", editable=False, rely=4, relx=2, color="SAFE" , highlighted=True)
self.fields["ogmercureUrl"] = {
"widget": self.add(
npyscreen.Textfield,
value=f"https://{self.server_ip}:3000/.well-known/mercure",
rely=6,
relx=18# Ajustar la posición vertical
)
}
def on_ok(self):
"""Guarda la configuración y pasa al siguiente formulario."""
# Obtener la configuración del formulario
component_config = {
"ogcoreUrl": self.fields["ogcoreUrl"]["widget"].value,
"ogmercureUrl": self.fields["ogmercureUrl"]["widget"].value,
}
# Guardar la configuración en el diccionario global
self.parentApp.configurations[self.component_name] = component_config
# Continuar con el siguiente formulario
self.parentApp.current_component_index += 1
if self.parentApp.current_component_index < len(self.parentApp.selected_components):
next_component = self.parentApp.selected_components[self.parentApp.current_component_index]
self.parentApp.switchForm(next_component)
else:
self.parentApp.generate_debconf()
self.parentApp.setNextForm(None)
class OgDhcpForm(ComponentForm):
component_name = "ogdhcp"
def get_dhcp_ip(self):
"""Obtiene la IP del servidor DHCP."""
if self.parentApp.installation_type == "mononodo":
return self.parentApp.server_ip
elif self.parentApp.installation_type == "multinodo":
return self.parentApp.dhcp_ip
def get_boot_ip(self):
"""Obtiene la IP del servidor Boot."""
if self.parentApp.installation_type == "mononodo":
return self.parentApp.server_ip
elif self.parentApp.installation_type == "multinodo":
return self.parentApp.boot_ip
def configure_fields(self):
# Obtener las interfaces de red disponibles
available_interfaces = get_network_interfaces().split(",")
# Selector de interfaces con altura ajustada
self.fields["dhcp_interfaces"] = {
"widget": self.add(
npyscreen.TitleMultiSelect,
name="Selecciona la interfaz de DHCP:",
values=available_interfaces,
scroll_exit=True,
rely=2,
max_height=5 # Reducir la altura para dejar espacio
)
}
# Campo para la IP del servidor DHCP
self.fields["ip"] = {
"widget": self.add(
npyscreen.TitleText,
name="IP del servidor DHCP:",
value=self.get_dhcp_ip(),
rely=8 # Ajustar la posición vertical
)
}
# Campo para la IP del servidor Boot
self.fields["ogbootIP"] = {
"widget": self.add(
npyscreen.TitleText,
name="IP del servidor Boot:",
value=self.get_boot_ip(),
rely=10 # Ajustar la posición vertical
)
}
def on_ok(self):
available_interfaces = self.fields["dhcp_interfaces"]["widget"].values
selected_indices = self.fields["dhcp_interfaces"]["widget"].value
# Validar que al menos un interfaz esté seleccionado
if not selected_indices or len(selected_indices) == 0:
npyscreen.notify_confirm("Debes seleccionar al menos una interfaz de red para DHCP.", title="Error")
return
try:
for i in selected_indices:
if i < 0 or i >= len(available_interfaces):
raise IndexError("Índice fuera de rango")
selected_interfaces = [available_interfaces[i] for i in selected_indices]
logging.debug(f"Interfaces seleccionadas: {selected_interfaces}")
interfaces_string = ",".join(selected_interfaces)
logging.debug(f"Interfaces seleccionadas: {interfaces_string}")
except (IndexError, ValueError):
npyscreen.notify_confirm("Selección inválida. Por favor, revisa los índices ingresados.", title="Error")
return
# Guardar las configuraciones
self.parentApp.configurations[self.component_name] = {
"interfaces": interfaces_string,
"ip": self.fields["ip"]["widget"].value,
"ogbootIP": self.fields["ogbootIP"]["widget"].value,
}
# Continuar con el siguiente formulario
self.parentApp.current_component_index += 1
if self.parentApp.current_component_index < len(self.parentApp.selected_components):
next_component = self.parentApp.selected_components[self.parentApp.current_component_index]
self.parentApp.switchForm(next_component)
else:
self.parentApp.generate_debconf()
self.parentApp.setNextForm(None)
class OgBootForm(ComponentForm):
component_name = "ogboot"
download_url = "https://ognproject.evlt.uma.es/oglive/"
def get_boot_ip(self):
"""Obtiene la IP del servidor Boot."""
if self.parentApp.installation_type == "mononodo":
return self.parentApp.server_ip
elif self.parentApp.installation_type == "multinodo":
return self.parentApp.boot_ip
def get_core_ip(self):
"""Obtiene la IP del servidor Core."""
if self.parentApp.installation_type == "mononodo":
return self.parentApp.server_ip
elif self.parentApp.installation_type == "multinodo":
return self.parentApp.core_ip
def configure_fields(self):
# Obtener la lista de oglives
oglives = get_oglive_list()
if not oglives:
oglives = ["https://ognproject.evlt.uma.es/oglive/ogLive-noble-6.8.0-31-generic-amd64-r20250116.538e3fa_20250120.iso"]
npyscreen.notify_confirm("No se pudo obtener la lista de oglives. Usando un valor por defecto.", title="Error")
# Campo para seleccionar un oglive
self.fields["ogliveUrl"] = {
"widget": self.add(
npyscreen.TitleSelectOne,
name="Selecciona un OgLive:",
values=oglives,
scroll_exit=True,
max_height=6,
rely=2 # Ajustar la posición vertical
)
}
# Otros campos
#self.fields["ip"] = {"widget": self.add(npyscreen.TitleText, name="IP del servidor Boot:", value=self.get_boot_ip(), rely=14)}
self.add(npyscreen.FixedText, value="IP del servidor Boot:", editable=False, rely=10, relx=2, color="SAFE" , highlighted=True)
self.fields["ip"] = {
"widget": self.add(
npyscreen.Textfield,
value=self.get_boot_ip(),
rely=11, # Línea siguiente
relx=18,
highlighted=True
)
}
# self.fields["ogcoreUrl"] = {"widget": self.add(npyscreen.TitleText, name="URL OgCore:", value=f"https://{self.get_core_ip()}:8443", rely=18)}
self.add(npyscreen.FixedText, value="IP del servidor Core:", editable=False, rely=12, relx=2, color="SAFE" , highlighted=True)
self.fields["ogcoreUrl"] = {
"widget": self.add(
npyscreen.Textfield,
value=f"https://{self.get_core_ip()}:8443",
rely=13, # Línea siguiente
relx=18,
highlighted=True
)
}
# self.fields["sambaUser"] = {"widget": self.add(npyscreen.TitleText, name="Usuario Samba:", value="opengnsys", rely=20)}
self.add(npyscreen.FixedText, value="Usuario Samba:", editable=False, rely=14, relx=2, color="SAFE" , highlighted=True)
self.fields["sambaUser"] = {
"widget": self.add(
npyscreen.Textfield,
value="opengnsys",
rely=15, # Línea siguiente
relx=18,
highlighted=True
)
}
#self.fields["sambaUserPass"] = {"widget": self.add(npyscreen.TitlePassword, name="Contraseña Samba:", value="og", rely=22)}
self.fields["sambaUserPass"] = {
"widget": self.add(
npyscreen.TitlePassword,
name="Contraseña Samba:",
value="og",
rely=16, # Línea siguiente
)
}
#self.fields["port"] = {"widget": self.add(npyscreen.TitleText, name="Puerto Boot:", value="8082",hidden=True, rely=16)}
self.add(npyscreen.FixedText, value="Puerto Boot:", editable=False, rely=12, relx=2, color="SAFE" , highlighted=True,hidden=True)
self.fields["port"] = {
"widget": self.add(
npyscreen.Textfield,
value="8082",
rely=13, # Línea siguiente
relx=18,
hidden=True
)
}
def on_ok(self):
# Obtener el oglive seleccionado
selected_oglive_index = self.fields["ogliveUrl"]["widget"].value
if not selected_oglive_index or len(selected_oglive_index) == 0:
npyscreen.notify_confirm("Debes seleccionar una imagen OgLive.", title="Error")
return
selected_oglive = self.fields["ogliveUrl"]["widget"].values[selected_oglive_index[0]]
# Guardar las configuraciones
self.parentApp.configurations[self.component_name] = {
"ogliveUrl": self.download_url + selected_oglive,
"ip": self.fields["ip"]["widget"].value,
"port": self.fields["port"]["widget"].value,
"ogcoreUrl": self.fields["ogcoreUrl"]["widget"].value,
"sambaUser": self.fields["sambaUser"]["widget"].value,
"sambaUserPass": self.fields["sambaUserPass"]["widget"].value,
}
# Continuar con el siguiente formulario
self.parentApp.current_component_index += 1
if self.parentApp.current_component_index < len(self.parentApp.selected_components):
next_component = self.parentApp.selected_components[self.parentApp.current_component_index]
self.parentApp.switchForm(next_component)
else:
self.parentApp.generate_debconf()
self.parentApp.setNextForm(None)
class OgRepositoryForm(ComponentForm):
component_name = "ogrepository"
def get_repo_ip(self):
"""Obtiene la IP del servidor Repository."""
if self.parentApp.installation_type == "mononodo":
return self.parentApp.server_ip
elif self.parentApp.installation_type == "multinodo":
return self.parentApp.repo_ip
def get_core_ip(self):
"""Obtiene la IP del servidor Core."""
if self.parentApp.installation_type == "mononodo":
return self.parentApp.server_ip
elif self.parentApp.installation_type == "multinodo":
return self.parentApp.core_ip
def configure_fields(self):
# Campo para la IP del Repositorio
self.add(npyscreen.FixedText, value="IP del Repositorio:", editable=False, rely=2, relx=2, color="SAFE" , highlighted=True)
self.fields["ogrepoIp"] = {
"widget": self.add(
npyscreen.Textfield,
value=self.get_repo_ip(),
rely=3, # Línea siguiente
relx=18,
highlighted=True
)
}
# Campo para la IP de OgCore
self.add(npyscreen.FixedText, value="IP de OgCore:", editable=False, rely=5, relx=2, color="SAFE" , highlighted=True)
self.fields["ogcoreIp"] = {
"widget": self.add(
npyscreen.Textfield,
value=self.get_core_ip(),
rely=6, # Línea siguiente
relx=18,
)
}
# Campo para el Usuario Samba
self.add(npyscreen.FixedText, value="Usuario Samba:", editable=False, rely=8, relx=2, color="SAFE" , highlighted=True)
self.fields["sambaUser"] = {
"widget": self.add(
npyscreen.Textfield,
value="opengnsys",
rely=9, # Línea siguiente
relx=18 ,
)
}
# Campo para la Contraseña Samba
self.fields["sambaUserPass"] = {
"widget": self.add(
npyscreen.TitlePassword,
name="Contraseña Samba:",
value="og",
rely=11 # Mantener el uso de TitlePassword
)
}
class InstallationProgressForm(npyscreen.Form):
"""Formulario para mostrar el progreso de instalación y el log en tiempo real."""
def create(self):
# Ajustar alturas para evitar problemas de espacio
self.progress_box = self.add(
npyscreen.BoxTitle,
name="Progreso de instalación",
max_height=6,
rely=1,
relx=2,
scroll_exit=True
)
self.log_box = self.add(
npyscreen.BoxTitle,
name="Log de instalación",
max_height=8,
rely=8,
relx=2,
scroll_exit=True
)
def update_progress(self, message, current=0, total=0):
"""Actualiza el progreso de instalación en la parte superior."""
if total > 0:
# Crear una barra de progreso personalizada
progress_percentage = int((current / total) * 100)
bar_length = 30 # Longitud de la barra
filled_length = int(bar_length * current // total)
bar = f"[{'=' * filled_length}{' ' * (bar_length - filled_length)}] {progress_percentage}%"
self.progress_box.values.append(bar)
self.progress_box.values.append(message)
self.progress_box.display()
def update_log(self, new_line):
"""Actualiza el log en tiempo real en la parte inferior."""
max_lines = 10 # Número máximo de líneas a mostrar
self.log_box.values.append(new_line.strip()) # Agregar la nueva línea
self.log_box.values = self.log_box.values[-max_lines:] # Mantener solo las últimas `max_lines` líneas
self.log_box.display()
def install_components_with_ui(form, components, selected_tag):
"""Instala los componentes seleccionados mostrando el progreso y el log en tiempo real."""
log_file_path = os.path.join(LOGS_DIR, "installation.log")
installed_packages = []
failed_packages = []
start_time = time.time()
try:
with open(log_file_path, "w", buffering=1) as log_file:
total_packages = len(components)
def tail_log():
try:
with open(log_file_path, "r") as log_reader:
log_reader.seek(0, os.SEEK_END)
while True:
line = log_reader.readline()
if line:
form.update_log(line)
except Exception as e:
# Mostrar error en log si ocurre
try:
form.update_log(f"[ERROR] {e}")
except Exception:
pass
log_thread = threading.Thread(target=tail_log, daemon=True)
log_thread.start()
for index, package in enumerate(components, start=1):
form.update_progress(f"Instalando paquete {index}/{total_packages}: {package}", current=index, total=total_packages)
install_command = f"DEBIAN_FRONTEND=noninteractive apt-get install -y {package}"
process = subprocess.Popen(
install_command, shell=True, text=True, stdout=log_file, stderr=log_file, bufsize=1
)
process.wait()
log_file.flush()
if process.returncode != 0:
error_message = f"Error al instalar el paquete {package}. Consulta el archivo de registro: {log_file_path}"
form.update_progress(error_message)
failed_packages.append(package)
else:
form.update_progress(f"Paquete {package} instalado correctamente.")
installed_packages.append(package)
if package == "ogboot":
form.update_progress("Instalando paquete adicional: ogclient")
install_command = "DEBIAN_FRONTEND=noninteractive apt-get install -y ogclient"
process = subprocess.Popen(
install_command, shell=True, text=True, stdout=log_file, stderr=log_file, bufsize=1
)
process.wait()
log_file.flush()
if process.returncode != 0:
error_message = f"Error al instalar el paquete ogclient. Consulta el archivo de registro: {log_file_path}"
form.update_progress(error_message)
failed_packages.append("ogclient")
else:
form.update_progress("Paquete ogclient instalado correctamente.")
installed_packages.append("ogclient")
except Exception as e:
try:
form.update_progress(f"Error durante la instalación: {e}")
except Exception:
print(f"[ERROR] {e}")
failed_packages.append("Error general durante la instalación")
end_time = time.time()
duration = end_time - start_time
summary = "\n--- Resumen de la instalación ---\n"
summary += f"Tiempo total de instalación: {duration:.2f} segundos\n"
summary += f"Paquetes instalados correctamente: {len(installed_packages)}\n"
for pkg in installed_packages:
summary += f" - {pkg}\n"
if failed_packages:
summary += f"\nPaquetes que fallaron: {len(failed_packages)}\n"
for pkg in failed_packages:
summary += f" - {pkg}\n"
else:
summary += "\nTodos los paquetes se instalaron correctamente.\n"
summary += f"\nConsulta el archivo de registro para más detalles: {log_file_path}"
# Mostrar el resumen y salir del formulario
try:
npyscreen.notify_confirm(summary, title="Resumen de la instalación", wide=True)
except Exception as e:
print(summary)
print(f"[ERROR] {e}")
# Forzar la salida de la aplicación después del resumen
import sys
sys.exit(0)
class MyApp(npyscreen.NPSAppManaged):
def onStart(self):
# Inicializar variables globales
self.installation_type = None # Tipo de instalación seleccionado (mononodo o multinodo)
self.server_ip = None # IP del servidor para mononodo
self.repo_ip = None # IP del servidor Repository
self.dhcp_ip = None # IP del servidor DHCP
self.core_ip = None # IP del servidor Core
self.boot_ip = None # IP del servidor Bootdpkg
self.selected_components = [] # Componentes seleccionados
self.selected_tag = None # Versión seleccionada
self.configurations = {} # Configuraciones de los componentes
# Registrar los formularios
self.addForm("INSTALLATION_TYPE", InstallationTypeForm)
self.addForm("MONONODO_CONFIG", MononodoConfigForm)
self.addForm("MULTINODO_CONFIG", MultinodoConfigForm)
self.addForm("MAIN", ComponentSelectionForm)
self.addForm("ogcore", OgCoreForm)
self.addForm("oggui", OgGuiForm)
self.addForm("ogdhcp", OgDhcpForm)
self.addForm("ogboot", OgBootForm)
self.addForm("ogrepository", OgRepositoryForm)
self.addForm("INSTALLATION_PROGRESS", InstallationProgressForm)
# Configurar el formulario inicial
self.setNextForm("INSTALLATION_TYPE")
def generate_debconf(self):
# Comprobar si la clave pública ya existe
logging.debug("Entrando en generate_debconf")
key_path = "/etc/apt/trusted.gpg.d/opengnsys.gpg"
if os.path.exists(key_path):
# Silenciar este mensaje
pass
else:
# Añadir la clave pública
try:
logging.debug("Añadiendo la clave pública")
subprocess.run(
'curl -k -L https://ognproject.evlt.uma.es/debian-opengnsys/public.key | gpg --dearmour -o /etc/apt/trusted.gpg.d/opengnsys.gpg',
shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True
)
except subprocess.CalledProcessError:
logging.error("Error al añadir la clave pública")
return
# Añadir el repositorio
try:
logging.debug("Añadiendo el repositorio")
selected_tag = self.selected_tag # Obtener el tag seleccionado
# Determinar el valor de repo_line según el argumento recibido
if len(sys.argv) > 1 and sys.argv[1].lower() == "devel":
repo_line = f'deb http://ognproject.evlt.uma.es/debian-opengnsys/opengnsys-devel/{selected_tag} noble main'
elif len(sys.argv) > 1 and sys.argv[1].lower() == "nightly":
repo_line = f'deb http://ognproject.evlt.uma.es/debian-opengnsys/nightly/main noble main'
else:
repo_line = f'deb http://ognproject.evlt.uma.es/debian-opengnsys/opengnsys/{selected_tag} noble main'
with open('/etc/apt/sources.list.d/opengnsys.list', 'w') as repo_file:
repo_file.write(repo_line + '\n')
except Exception:
logging.error("Error al añadir el repositorio")
print("Error al añadir el repositorio")
return
# Crear el archivo de versión instalada
try:
logging.debug("Creando el archivo de versión instalada")
os.makedirs("/opt/opengnsys", exist_ok=True)
with open("/opt/opengnsys/release", "w") as release_file:
release_file.write(f"Versión instalada: {selected_tag}\n")
except Exception as e:
print(f"Error al crear el archivo de versión: {e}")
# Actualizar los repositorios
try:
logging.debug("Actualizando los repositorios")
subprocess.run('apt-get update', shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True)
except subprocess.CalledProcessError:
# Silenciar errores
return
# Generar configuraciones para debconf
output_file = os.path.join(CONFIGS_DIR, "configurations.txt")
try:
with open(output_file, "w") as f:
f.write("\n--- Configuraciones para debconf-set-selections ---\n")
for component, config in self.configurations.items():
for key, value in config.items():
field_type = "password" if "Pass" in key else "string"
line = f'echo "{component} opengnsys/{component}_{key} {field_type} {value}" | debconf-set-selections\n'
f.write(line)
# Ejecutar la línea directamente y redirigir la salida a /dev/null
subprocess.run(
line, shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
)
logging.debug(f"Configuraciones guardadas en: {output_file}")
except Exception:
# Silenciar errores
logging.error(f"Error al guardar configuraciones en {output_file}")
print(f"Error al guardar configuraciones en {output_file}")
# Silenciar el mensaje de configuraciones guardadas
# print(f"\nConfiguraciones guardadas en: {output_file}")
# Llamar al formulario de progreso
form = self.getForm("INSTALLATION_PROGRESS")
self.switchForm("INSTALLATION_PROGRESS")
install_components_with_ui(form, self.selected_components, self.selected_tag)
def check_terminal_size(min_width=80, min_height=24):
"""Verifica si el tamaño del terminal es suficiente."""
terminal_size = shutil.get_terminal_size()
if terminal_size.columns < min_width or terminal_size.lines < min_height:
print(f"[ERROR] El tamaño del terminal es demasiado pequeño. Se requiere al menos {min_width}x{min_height}.")
exit(1)
if __name__ == "__main__":
try:
# Verificar el tamaño del terminal antes de iniciar la aplicación
check_terminal_size()
logging.debug("Ejecutando la aplicación principal")
MyApp().run()
except RuntimeError as e:
print(f"[ERROR] {e}")
logging.error(f"[ERROR] {e}")
exit(1) # Salir con un código de error
except KeyboardInterrupt:
logging.warning("El programa fue interrumpido por el usuario (Ctrl-C)")
except Exception as e:
logging.error(f"[ERROR] Ocurrió un error inesperado: {e}")
print(f"[ERROR] {e}")
finally:
# Asegurarse de que todos los mensajes de log se escriban en el archivo
logging.debug("Finalizando el programa y cerrando el log")
logging.shutdown()
# Restaurar el terminal al estado normal
npyscreen.wrapper_basic(lambda stdscr: None)

View File

@ -0,0 +1,220 @@
import curses
import json
import os
from git import Repo
CONFIGS_DIR = "/tmp/oginstall"
os.makedirs(CONFIGS_DIR, exist_ok=True)
REPO_URL = "https://ognproject.evlt.uma.es/gitea/opengnsys/ogcore.git"
def get_git_tags():
try:
repo_path = os.path.join(CONFIGS_DIR, "opengnsys_repo")
if not os.path.exists(repo_path):
print("Clonando el repositorio...")
Repo.clone_from(REPO_URL, repo_path)
else:
print("Usando repositorio existente en", repo_path)
repo = Repo(repo_path)
tags = [tag.name for tag in repo.tags]
if tags:
print("Tags encontrados:", tags)
else:
print("No se encontraron tags con el patrón especificado.")
return tags
except Exception as e:
print("Error al obtener los tags:", str(e))
return []
def get_password(stdscr, y, x, prompt, default=""):
stdscr.addstr(y, x, prompt, curses.color_pair(1))
password = ""
masked_password = ""
stdscr.move(y, x + len(prompt)) # Coloca el cursor después del prompt
while True:
key = stdscr.getch()
if key in (curses.KEY_BACKSPACE, 127): # Maneja el retroceso
if len(password) > 0:
password = password[:-1]
masked_password = "*" * len(password)
stdscr.move(y, x + len(prompt)) # Mueve el cursor después del prompt
stdscr.addstr(y, x + len(prompt), " " * (len(masked_password) + 1)) # Borra la línea
stdscr.addstr(y, x + len(prompt), masked_password) # Vuelve a mostrar los asteriscos actualizados
elif key == ord("\n"): # Confirmar con Enter
if not password and default: # Si el usuario no ingresó nada, usa el valor predeterminado
password = default
break
elif 32 <= key <= 126: # Rango de caracteres imprimibles
password += chr(key)
masked_password = "*" * len(password)
stdscr.addstr(y, x + len(prompt), masked_password) # Muestra asteriscos
return password
def get_input(stdscr, y, x, prompt, default=""):
max_y, max_x = stdscr.getmaxyx()
if x + len(prompt) >= max_x:
raise ValueError("El prompt es demasiado largo para caber en la pantalla.")
stdscr.addstr(y, x, prompt, curses.color_pair(1))
input_text = ""
prompt_end_x = x + len(prompt) # Calcula la posición final del prompt
stdscr.move(y, prompt_end_x) # Coloca el cursor después del prompt
while True:
key = stdscr.getch()
if key in (curses.KEY_BACKSPACE, 127): # Maneja el retroceso
if len(input_text) > 0:
input_text = input_text[:-1]
stdscr.move(y, prompt_end_x) # Mueve el cursor después del prompt
stdscr.clrtoeol() # Limpia la línea desde la posición actual hacia el final
stdscr.addstr(y, prompt_end_x, input_text) # Vuelve a mostrar el texto actualizado
stdscr.move(y, prompt_end_x + len(input_text))
elif key == ord("\n"): # Confirmar con Enter
if not input_text and default: # Usa el valor predeterminado si está vacío
input_text = default
break
elif 32 <= key <= 126: # Rango de caracteres imprimibles
if prompt_end_x + len(input_text) < max_x - 1:
input_text += chr(key)
stdscr.addstr(y, prompt_end_x, input_text) # Muestra el texto actualizado
stdscr.move(y, prompt_end_x + len(input_text)) # Mueve el cursor al final del texto
return input_text
def main(stdscr):
# Inicializar colores
curses.start_color()
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE)
stdscr.bkgd(' ', curses.color_pair(1))
curses.curs_set(0)
stdscr.clear()
# Paso 1: Seleccionar componentes
components = ["ogCore", "ogGui", "ogDhcp", "ogBoot", "ogRepository"]
selected_components = []
current_index = 0
# Mostrar instrucciones y opciones de componentes
stdscr.addstr(1, 2, "Selecciona los componentes (usa Flechas para navegar, Espacio para seleccionar, Enter para continuar):", curses.color_pair(1) | curses.A_BOLD)
while True:
for idx, comp in enumerate(components):
if comp in selected_components:
stdscr.addstr(idx + 3, 4, f"[X] {comp}", curses.color_pair(1))
else:
stdscr.addstr(idx + 3, 4, f"[ ] {comp}", curses.color_pair(1))
stdscr.addstr(current_index + 3, 4, f"> {components[current_index]}", curses.color_pair(1))
key = stdscr.getch()
if key == curses.KEY_UP and current_index > 0:
current_index -= 1
elif key == curses.KEY_DOWN and current_index < len(components) - 1:
current_index += 1
elif key == ord(" "):
component = components[current_index]
if component in selected_components:
selected_components.remove(component)
else:
selected_components.append(component)
elif key == ord("\n"):
break
stdscr.refresh()
# Menu de selección de releases
tags = get_git_tags()
tag_index = 0
stdscr.clear()
while True:
for idx, tag in enumerate(tags):
if idx == tag_index:
stdscr.addstr(idx + 3, 4, f"> {tag}", curses.color_pair(1))
else:
stdscr.addstr(idx + 3, 4, f" {tag}", curses.color_pair(1))
key = stdscr.getch()
if key == curses.KEY_UP and tag_index > 0:
tag_index -= 1
elif key == curses.KEY_DOWN and tag_index < len(tags) - 1:
tag_index += 1
elif key == ord("\n"):
break
stdscr.refresh()
# Configuración específica de cada componente seleccionado
curses.echo()
for component in selected_components:
stdscr.clear()
stdscr.addstr(1, 2, f"Configuración para {component}:", curses.color_pair(1) | curses.A_BOLD)
curses.curs_set(1)
config_data = {}
if component == "ogCore":
user = get_input(stdscr, 3, 0, "Usuario administrador (ogadmin): ", "ogadmin")
password = get_password(stdscr, 4, 0, "Contraseña (por defecto '12345678'): ", "12345678")
config_data = {"username": user, "password": password, "container_version": tags[tag_index]}
elif component == "ogGui":
ogcore_ip = get_input(stdscr, 3, 0, "URL Api OgCore (https://127.0.0.1:8443): " , "https://127.0.0.1:8443")
config_data = {"ogcore_ip": ogcore_ip, "container_version": tags[tag_index]}
elif component == "ogDhcp":
ogbootIP = get_input(stdscr, 3, 0, "IP servidor de Boot (127.0.0.1): ", "127.0.0.1")
ogdhcpIP = get_input(stdscr, 4, 0, "IP servidor de DHCP (127.0.0.1): ", "127.0.0.1")
ogdhcpDir = get_input(stdscr, 5, 0, "Directorio de ogdhcp (/opt/opengnsys/ogdhcp): ", "/opt/opengnsys/ogdhcp")
interfaces = get_input(stdscr, 6, 0, "Interfaces Boot (eth0,eth1): ", "eth0,eth1")
json_array_interfaces = interfaces.split(",")
config_data = {"ogbootIP": ogbootIP, "ogDhcpIP": ogdhcpIP , "ogDhcp_Dir" : ogdhcpDir , "interfaces": json_array_interfaces, "release": tags[tag_index]}
elif component == "ogBoot":
ogcore_ip = get_input(stdscr, 3, 0, "ogCore Ip Server: ", "")
ogboot_server_ip = get_input(stdscr, 4, 0, "ogBoot Server IP: ", "")
ogcore_dir = get_input(stdscr, 5, 0, "ogCore Dir (/opt/opengnsys/ogboot): ", "/opt/opengnsys/ogboot")
ogLive_default = get_input(stdscr, 6, 0, "ogLive por defecto (ogLive-noble-6.8.0-31-generic-amd64-r20241128.62778c9_20241129): ", "ogLive-noble-6.8.0-31-generic-amd64-r20241128.62778c9_20241129")
ogboot_samba_user = get_input(stdscr, 7, 0, "ogBoot Samba User (opengnsys): ", "opengnsys")
ogboot_samba_pass = get_password(stdscr, 8, 0, "ogBoot Samba Pass (og): ", "og")
config_data = {
"ogCore_ServerIP": ogcore_ip,
"ogBoot_ServerIP": ogboot_server_ip,
"ogBoot_Dir": ogcore_dir,
"ogLive_Default": "https://ognproject.evlt.uma.es/oglive/" + ogLive_default + ".iso",
"ogBootSambaUser": ogboot_samba_user,
"ogBootSambaPass": ogboot_samba_pass,
"release": tags[tag_index]
}
elif component == "ogRepository":
ogrepository_ip = get_input(stdscr, 3, 0, "ogRepository IP Server (127.0.0.1): ", "")
ogrepository_samba_user = get_input(stdscr, 4, 0, "ogRepository Sambauser (opengnsys): ", "opengnsys")
ogrepository_samba_pass = get_password(stdscr, 5, 0, "ogRepository Sambapass (og): ", "og")
config_data = {
"ogrepository_ip": ogrepository_ip,
"ogrepository_samba_user": ogrepository_samba_user,
"ogrepository_samba_pass": ogrepository_samba_pass,
"release": tags[tag_index]
}
# Guardar en archivo JSON
config_file = os.path.join(CONFIGS_DIR, f"config_{component}.json")
with open(config_file, "w") as f:
json.dump(config_data, f)
stdscr.clear()
stdscr.addstr(2, 2, f"Configuración de {component} guardada en {config_file}", curses.color_pair(1))
stdscr.refresh()
stdscr.getch()
curses.noecho() # Desactivar el eco después de la entrada
curses.wrapper(main)

View File

@ -0,0 +1,54 @@
#!/bin/bash
# Setup installer environment
BRANCH=${BRANCH:-main}
GIT_SSL_NO_VERIFY=1
GIT_REPO="https://ognproject.evlt.uma.es/gitea/api/v1/repos/opengnsys/oginstaller/archive/$BRANCH.zip"
export GIT_SSL_NO_VERIFY
install_packages() {
apt-get update
apt-get install -y curl jq unzip python3 python3-git
}
download_installer() {
rm -f /tmp/oginstaller.zip
rm -rf /tmp/oginstaller-$BRANCH
rm -rf /tmp/oginstaller
curl -q -k $GIT_REPO -H 'accept: application/json' -o /tmp/oginstaller.zip
unzip /tmp/oginstaller.zip -d /tmp
mv /tmp/oginstaller /tmp/oginstaller-$BRANCH
}
extract_installer() {
rm -rf /tmp/oginstall
mkdir -p /tmp/oginstall
cp -r /tmp/oginstaller-$BRANCH/python-installer/* /tmp/oginstall/
cp -r /tmp/oginstaller-$BRANCH/component-installer/* /tmp/oginstall/
chmod 755 /tmp/oginstall/*.sh
chmod 755 /tmp/oginstall/*.py
}
create_questions() {
echo "Creating questions..."
python3 /tmp/oginstall/oginstaller.py
}
launch_component_installer() {
echo "Launching component installer..."
/tmp/oginstall/component-installer.sh
}
install_packages
download_installer
extract_installer
create_questions
launch_component_installer

View File

@ -0,0 +1,311 @@
#!/usr/bin/env python3
import subprocess
import requests
import sys
import npyscreen
import re
import os
# Configuración general
REPO_BASE_URL = "http://ognproject.evlt.uma.es/debian-opengnsys/opengnsys-devel"
RELEASES_URL = "https://ognproject.evlt.uma.es/debian-opengnsys/versions-dev.json"
APT_LIST_PATH = "/etc/apt/sources.list.d/opengnsys.list"
PACKAGES = ["ogrepository", "ogcore", "oggui", "ogclient", "ogboot", "ogdhcp"]
RELEASE_FILE = "/opt/opengnsys/release"
# === Sección npyscreen ===
class ServerURLForm(npyscreen.Form):
def create(self):
self.server_url = self.add(npyscreen.TitleText, name="Servidor de validación (URL completa):", value="http://localhost:5000/validar")
def afterEditing(self):
self.parentApp.server_url = self.server_url.value
self.parentApp.setNextForm("RELEASE")
class ReleaseSelectorForm(npyscreen.ActionForm):
def create(self):
self.releases = self.parentApp.releases
self.listbox = self.add(npyscreen.TitleSelectOne,
name="Releases disponibles",
values=self.releases,
scroll_exit=True,
max_height=len(self.releases)+4)
def on_ok(self):
selected_index = self.listbox.value[0] if self.listbox.value else None
if selected_index is None:
npyscreen.notify_confirm("Debes seleccionar una release antes de continuar.", title="Error")
else:
self.parentApp.selected = self.releases[selected_index]
self.parentApp.setNextForm(None)
def on_cancel(self):
npyscreen.notify_confirm("Operación cancelada. Saliendo del formulario.", title="Cancelado")
self.parentApp.setNextForm(None)
class ReleaseSelectorApp(npyscreen.NPSAppManaged):
def __init__(self, releases):
self.releases = releases
self.selected = None
self.server_url = None
super().__init__()
def onStart(self):
self.addForm("MAIN", ServerURLForm, name="Configuración inicial")
self.addForm("RELEASE", ReleaseSelectorForm, name="Selecciona una release", releases=self.releases)
def choose_release_and_server(releases):
app = ReleaseSelectorApp(releases)
app.run()
return app.selected, app.server_url
# === Funciones principales ===
def backup_file(filepath):
"""Crea una copia de seguridad del archivo especificado."""
backup_path = f"{filepath}.bak"
if os.path.exists(filepath):
try:
os.replace(filepath, backup_path)
print(f"[INFO] Copia de seguridad creada: {backup_path}")
except Exception as e:
print(f"[ERROR] No se pudo crear la copia de seguridad de {filepath}: {e}")
return backup_path
def restore_file(backup_path, original_path):
"""Restaura el archivo desde su copia de seguridad."""
if os.path.exists(backup_path):
try:
os.replace(backup_path, original_path)
print(f"[INFO] Archivo restaurado: {original_path}")
except Exception as e:
print(f"[ERROR] No se pudo restaurar el archivo {original_path}: {e}")
def get_installed_packages():
installed = []
for pkg in PACKAGES:
try:
subprocess.run(
["dpkg-query", "-W", pkg],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
check=True
)
installed.append(pkg)
except subprocess.CalledProcessError:
continue
return installed
def get_installed_release():
try:
with open(RELEASE_FILE, "r") as release_file:
line = release_file.readline().strip()
match = re.search(r".*:\s*(.+)", line)
if match:
return match.group(1).strip()
except FileNotFoundError:
print("El archivo de release no existe.")
except Exception as e:
print(f"Error al leer el archivo de release: {e}")
return None
def fetch_available_releases():
try:
response = requests.get(RELEASES_URL)
response.raise_for_status()
data = response.json()
return data.get("versions", [])
except requests.RequestException as e:
print(f"[ERROR] No se pudo obtener la lista de releases: {e}")
sys.exit(1)
def update_repo_file(selected_release):
backup_path = backup_file(APT_LIST_PATH)
line = f"deb {REPO_BASE_URL}/{selected_release} noble main\n"
print(f"[INFO] Escribiendo nueva línea en {APT_LIST_PATH}:\n{line.strip()}")
try:
with open(APT_LIST_PATH, "w") as f:
f.write(line)
except PermissionError:
print("[ERROR] No tienes permisos para escribir en el archivo del repositorio. Ejecuta el script como root.")
restore_file(backup_path, APT_LIST_PATH)
sys.exit(1)
# Ejecutar apt update para actualizar la información del repositorio
try:
print("[INFO] Actualizando la información del repositorio con 'apt update'...")
subprocess.run(["sudo", "apt", "update"], check=True)
except subprocess.CalledProcessError as e:
print(f"[ERROR] Error al ejecutar 'apt update': {e}")
restore_file(backup_path, APT_LIST_PATH)
sys.exit(1)
def check_compatibility(server_url, installed_release, selected_release):
payload = {
"installed_release": installed_release,
"target_release": selected_release
}
try:
response = requests.post(server_url, json=payload, timeout=5)
response.raise_for_status()
result = response.json()
return result.get("compatible", False), result.get("message", "")
except requests.RequestException as e:
print(f"[ERROR] No se pudo contactar con el servidor de validación: {e}")
return False, str(e)
def summarize_updates(installed_packages, selected_release):
"""Genera un resumen de los paquetes que se van a actualizar y los que no."""
to_update = []
up_to_date = []
for pkg in installed_packages:
try:
result = subprocess.run(
["apt-cache", "policy", pkg],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
check=True,
env={"LANG": "C"} # Forzar el idioma a inglés
)
installed = None
candidate = None
for line in result.stdout.splitlines():
if "Installed:" in line: # Siempre estará en inglés
installed = line.split(":", 1)[1].strip()
elif "Candidate:" in line: # Siempre estará en inglés
candidate = line.split(":", 1)[1].strip()
if not installed or candidate == "(none)":
to_update.append(f"{pkg} (no instalado o sin versión candidata)")
elif installed != candidate:
to_update.append(f"{pkg} ({installed}{candidate})")
else:
up_to_date.append(f"{pkg} ({installed})")
except subprocess.CalledProcessError:
to_update.append(f"{pkg} (error obteniendo versión)")
summary = "\n--- Resumen de actualización ---\n"
summary += f"Release objetivo: {selected_release}\n\n"
summary += "Paquetes que se actualizarán:\n"
summary += "\n".join(f" - {line}" for line in to_update) if to_update else " - Ninguno\n"
summary += "\nPaquetes que ya están actualizados:\n"
summary += "\n".join(f" - {line}" for line in up_to_date) if up_to_date else " - Ninguno\n"
summary += "\n--------------------------------"
# Mostrar el resumen en una ventana emergente
npyscreen.notify_confirm(summary, title="Resumen de actualización", wide=True)
if not to_update:
npyscreen.notify_confirm("[INFO] Todos los paquetes están actualizados. No es necesario continuar.", title="Información")
sys.exit(0)
if not npyscreen.notify_yes_no("¿Deseas continuar con la actualización?", title="Confirmación"):
npyscreen.notify_confirm("[INFO] Actualización cancelada por el usuario.", title="Cancelado")
restore_file(f"{APT_LIST_PATH}.bak", APT_LIST_PATH)
restore_file(f"{RELEASE_FILE}.bak", RELEASE_FILE)
sys.exit(0)
return [line.split()[0] for line in to_update]
def show_final_versions(packages):
print("\n✅ Resumen final de versiones instaladas:")
for pkg in packages:
try:
result = subprocess.run(
["dpkg-query", "-W", "-f=${Version}", pkg],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True
)
version = result.stdout.strip()
print(f" - {pkg}: {version}")
except subprocess.CalledProcessError:
print(f" - {pkg}: no instalado")
def update_and_install(packages_to_update, selected_release):
backup_release = backup_file(RELEASE_FILE)
try:
subprocess.run(["sudo", "apt", "update"], check=True)
subprocess.run(["sudo", "apt", "install", "-y"] + packages_to_update, check=True)
print("[INFO] Paquetes actualizados correctamente.")
# Actualizar el archivo de release con la versión seleccionada
try:
os.makedirs(os.path.dirname(RELEASE_FILE), exist_ok=True)
with open(RELEASE_FILE, "w") as release_file:
release_file.write(f"Versión instalada: {selected_release}\n")
print(f"[INFO] Archivo de release actualizado: {selected_release}")
except Exception as e:
print(f"[ERROR] No se pudo actualizar el archivo de release: {e}")
show_final_versions(packages_to_update)
except subprocess.CalledProcessError as e:
print(f"[ERROR] Error al instalar paquetes: {e}")
restore_file(backup_release, RELEASE_FILE)
sys.exit(1)
# === Entrada principal ===
def main():
print("[INFO] Iniciando actualización de paquetes de OpenGnSys...")
installed_release = get_installed_release()
if installed_release:
print(f"[INFO] Versión instalada: {installed_release}")
else:
print("[WARN] No se encontró la versión instalada.")
sys.exit(1)
installed = get_installed_packages()
if not installed:
print("[ERROR] No se detectaron paquetes OpenGnSys instalados.")
sys.exit(1)
releases = fetch_available_releases()
selected, server_url = choose_release_and_server(releases)
if not selected or not server_url:
print("[WARN] No se seleccionó release o URL del servidor. Restaurando archivos.")
restore_file(f"{APT_LIST_PATH}.bak", APT_LIST_PATH)
restore_file(f"{RELEASE_FILE}.bak", RELEASE_FILE)
sys.exit(0)
print(f"[INFO] Validando compatibilidad con {server_url}...")
compatible, message = check_compatibility(server_url, installed_release, selected)
if not compatible:
print(f"[ERROR] El servidor indica que la actualización no es compatible: {message}")
restore_file(f"{APT_LIST_PATH}.bak", APT_LIST_PATH)
restore_file(f"{RELEASE_FILE}.bak", RELEASE_FILE)
sys.exit(1)
else:
print(f"[INFO] Compatibilidad validada: {message}")
try:
update_repo_file(selected)
to_update = summarize_updates(installed, selected)
update_and_install(to_update, selected)
except Exception as e:
print(f"[ERROR] Error durante la actualización: {e}")
restore_file(f"{APT_LIST_PATH}.bak", APT_LIST_PATH)
restore_file(f"{RELEASE_FILE}.bak", RELEASE_FILE)
sys.exit(1)
if __name__ == "__main__":
try:
main()
except SystemExit as e:
# Manejar la excepción SystemExit para evitar interrupciones
if e.code != 0:
print(f"[INFO] El script terminó con código de salida: {e.code}")
except Exception as e:
print(f"[ERROR] Ocurrió un error inesperado: {e}")
finally:
# Restaurar el terminal al estado normal
npyscreen.wrapper_basic(lambda stdscr: None)

View File

@ -0,0 +1,75 @@
#!/bin/bash
# Setup installer environment
BRANCH=${BRANCH:-main}
GIT_SSL_NO_VERIFY=1
GIT_REPO="https://ognproject.evlt.uma.es/gitea/api/v1/repos/opengnsys/oginstaller/archive/$BRANCH.zip"
export GIT_SSL_NO_VERIFY
DEVEL=$1
if [ "$DEVEL" == "devel" ]; then
INSTALL_DEVEL=1
elif [ "$DEVEL" == "nightly" ]; then
INSTALL_NIGHTLY=1
else
INSTALL_STABLE=1
fi
install_packages() {
apt-get update
apt-get install -y curl jq unzip python3 python3-git
}
create_python_venv() {
apt-get install -y python3-venv
python3 -m venv /tmp/oginstall/venv
source /tmp/oginstall/venv/bin/activate
pip install -r /tmp/oginstall/requirements.txt
}
download_installer() {
rm -f /tmp/oginstaller.zip
rm -rf /tmp/oginstaller-$BRANCH
rm -rf /tmp/oginstaller
curl -q -k $GIT_REPO -H 'accept: application/json' -o /tmp/oginstaller.zip
unzip /tmp/oginstaller.zip -d /tmp
mv /tmp/oginstaller /tmp/oginstaller-$BRANCH
}
extract_installer() {
rm -rf /tmp/oginstall
mkdir -p /tmp/oginstall
cp -r /tmp/oginstaller-$BRANCH/non_graf_installer/python-installer/* /tmp/oginstall/
chmod 755 /tmp/oginstall/*.py
}
create_questions() {
echo "Creating questions..."
if [ $INSTALL_DEVEL ] ; then
python3 /tmp/oginstall/oginstaller-v3.py devel
elif [ $INSTALL_NIGHTLY ] ; then
python3 /tmp/oginstall/oginstaller-v3.py nightly
else
python3 /tmp/oginstall/oginstaller-v3.py
fi
deactivate
}
clean_tmp() {
rm -rf /tmp/oginstall
rm -rf /tmp/oginstaller-$BRANCH
rm -f /tmp/oginstaller.zip
}
install_packages
download_installer
extract_installer
create_python_venv
create_questions
clean_tmp

View File

@ -0,0 +1,70 @@
#!/bin/bash
# Setup installer environment
BRANCH=${BRANCH:-main}
GIT_SSL_NO_VERIFY=1
GIT_REPO="https://ognproject.evlt.uma.es/gitea/api/v1/repos/opengnsys/oginstaller/archive/$BRANCH.zip"
export GIT_SSL_NO_VERIFY
install_packages() {
apt-get update
apt-get install -y curl jq unzip python3 python3-git
}
create_python_venv() {
apt-get install -y python3-venv
python3 -m venv /tmp/oginstall/venv
source /tmp/oginstall/venv/bin/activate
pip install -r /tmp/oginstall/requirements.txt
}
download_installer() {
rm -f /tmp/oginstaller.zip
rm -rf /tmp/oginstaller-$BRANCH
rm -rf /tmp/oginstaller
curl -q -k $GIT_REPO -H 'accept: application/json' -o /tmp/oginstaller.zip
unzip /tmp/oginstaller.zip -d /tmp
mv /tmp/oginstaller /tmp/oginstaller-$BRANCH
}
extract_installer() {
rm -rf /tmp/oginstall
mkdir -p /tmp/oginstall
cp -r /tmp/oginstaller-$BRANCH/non_graf_installer/python-installer/* /tmp/oginstall/
cp -r /tmp/oginstaller-$BRANCH/non_graf_installer/component-installer/* /tmp/oginstall/
chmod 755 /tmp/oginstall/*.sh
chmod 755 /tmp/oginstall/*.py
}
create_questions() {
echo "Creating questions..."
python3 /tmp/oginstall/oginstaller-v2.py
deactivate
}
launch_component_installer() {
echo "Launching component installer..."
/tmp/oginstall/component-installer.sh
}
clean_tmp() {
rm -rf /tmp/oginstall
rm -rf /tmp/oginstaller-$BRANCH
rm -f /tmp/oginstaller.zip
}
install_packages
download_installer
extract_installer
create_python_venv
create_questions
launch_component_installer
clean_tmp

View File

@ -0,0 +1,4 @@
GitPython
npyscreen
requests
bs4

View File

@ -0,0 +1,214 @@
#!/bin/bash
# Detect installed components.
INSTALLER_BRANCH=${INSTALLER_BRANCH:-main}
OPENGNSYS_BASE=/opt/opengnsys
OPENGNSYS_COMPONENTS=(ogCore ogGui ogDhcp ogBoot ogRepository)
GIT_SSL_NO_VERIFY=1
INSTALLED_COMPONENTS=()
GIT_REPO="https://ognproject.evlt.uma.es/gitea/api/v1/repos/opengnsys/oginstaller/archive/$INSTALLER_BRANCH.zip"
export GIT_SSL_NO_VERIFY
INSTALLED_COMPONENTS=()
check_os(){
if [ -f /etc/os-release ]; then
. /etc/os-release
# Just support Ubuntu 24.04 for now
if [ $ID == "ubuntu" ] && [ $VERSION_ID == "24.04" ]; then
echo "OS supported."
else
echo "OS not supported."
exit 1
fi
else
echo "OS not supported."
exit 1
fi
}
detect_installed_components() {
local OGNODE=0
for component in "${OPENGNSYS_COMPONENTS[@]}"; do
if [ -f "${OPENGNSYS_BASE}/${component}/installer/config.json" ]; then
echo "Component $component is installed."
INSTALLED_COMPONENTS+=($component)
OGNODE=1
else
echo "Component $component is not installed."
fi
done
if [ $OGNODE -eq 0 ]; then
echo "No OpenGnsys components installed."
else
echo "Installed components:" "${INSTALLED_COMPONENTS[@]}"
fi
}
# Assume taht all components are at the same release version, get the first installed compoenent and return its version
get_og_installed_version() {
local component=$1
local version=$(jq -r '.release' ${OPENGNSYS_BASE}/${component}/installer/config.json)
echo $version
}
start_stop_component() {
local component=$1
local action=$2
case $component in
ogCore)
handle_ogcore $action
;;
ogGui)
handle_oggui $action
;;
ogDhcp)
handle_ogdhcp $action
;;
ogBoot)
handle_ogboot $action
;;
ogRepository)
handle_ogrepository $action
;;
*)
echo "Component $component not found."
;;
esac
}
stop_installed_services() {
echo "Stopping services..."
for component in "${INSTALLED_COMPONENTS[@]}"; do
echo "Stopping component $component..."
start_stop_component $component stop
done
}
start_installed_services() {
echo "Starting services..."
for component in "${INSTALLED_COMPONENTS[@]}"; do
echo "Starting component $component..."
start_stop_component $component start
done
}
handle_ogboot() {
case $1 in
stop)
echo "Stopping ogBoot..."
systemctl stop nginx
systemctl stop tftpd-hpa
systemctl stop smbd
systemctl stop nmbd
;;
start)
echo "Starting ogBoot..."
systemctl start nginx
systemctl start tftpd-hpa
systemctl start smbd
systemctl start nmbd
;;
*)
echo "Invalid action."
;;
esac
}
handle_ogdhcp() {
case $1 in
stop)
echo "Stopping ogDhcp..."
systemctl stop kea-dhcp4-server
systemctl stop kea-ctrl-agent
;;
start)
echo "Starting ogDhcp..."
systemctl start kea-dhcp4-server
systemctl start kea-ctrl-agent
;;
*)
echo "Invalid action."
;;
esac
}
handle_ogrepository() {
case $1 in
stop)
echo "Stopping ogRepository..."
systemctl stop smbd
systemctl stop nmbd
systemctl stop ogrepo-api
;;
start)
echo "Starting ogRepository..."
systemctl start smbd
systemctl start nmbd
systemctl start ogrepo-api
;;
*)
echo "Invalid action."
;;
esac
}
handle_ogcore() {
case $1 in
stop)
echo "Stopping ogCore..."
systemctl stop ogcore
;;
start)
echo "Starting ogCore..."
systemctl start ogcore
;;
*)
echo "Invalid action."
;;
esac
}
handle_oggui() {
case $1 in
stop)
echo "Stopping ogGui..."
systemctl stop oggui-app
;;
start)
echo "Starting ogGui..."
systemctl start oggui-app
;;
*)
echo "Invalid action."
;;
esac
}
update_installed_components() {
local version=$1
echo "Updating components to version $version..."
for component in "${INSTALLED_COMPONENTS[@]}"; do
echo "Updating component $component..."
update_component $component $version
done
}
#### Main
check_os
detect_installed_components
installed_version=$(get_og_installed_version "${INSTALLED_COMPONENTS[0]}")
select_version_to_update
stop_installed_services
update_installed_components $installed_version
start_installed_services

View File

@ -1,104 +0,0 @@
#!/bin/bash
CONFIGS_DIR=/tmp/opengnsys-installer-configs
rm -rf $CONFIGS_DIR
mkdir -p $CONFIGS_DIR
# Paso 1: Seleccionar los componentes
components=$(yad --list --title="Seleccionar componentes" \
--text="Selecciona los componentes que deseas configurar:" \
--checklist --multiple \
--column="Seleccionar" --column="Componente" \
FALSE "ogCore" \
FALSE "ogGui" \
FALSE "ogDhcp" \
FALSE "ogBoot" \
--width=400 --height=300 --center)
# Verificar si el usuario seleccionó algún componente
if [[ -z "$components" ]]; then
yad --info --text="No seleccionaste ningún componente. Saliendo..." --center
exit 1
fi
for component in $components; do
selected_component=$(echo "$component" | cut -d '|' -f 2)
# Pedir la configuración específica para cada componente seleccionado
# Dividir la configuración en IP y ruta del fichero
config_file="config_${selected_component}.json"
case $selected_component in
"ogCore")
config=$(yad --form --title="Configuración para $selected_component" \
--field="Usuario administrador":TEXT \
--field="Contraseña":H \
--field="Tag del contenedor":TEXT \
"ogadmin" "" "latest" \
--width=400 --height=200 --center)
user=$(echo "$config" | cut -d '|' -f 1)
password=$(echo "$config" | cut -d '|' -f 2)
container_tag=$(echo "$config" | cut -d '|' -f 3)
echo "{\"username\": \"$user\", \"password\": \"$password\", \"container_version\": \"$container_tag\" }" > $CONFIGS_DIR/"$config_file"
;;
"ogGui")
config=$(yad --form --title="Configuración para $selected_component" \
--field="IP del servidor de ogCore" \
--field="Tag del contenedor":TEXT \
"" "latest" \
--width=400 --height=200 --center)
ogcore_ip=$(echo "$config" | cut -d '|' -f 1)
container_version=$(echo "$config" | cut -d '|' -f 2)
echo "{\"ogcore_ip\": \"$ogcore_ip\" , \"container_version\": \"$container_version\" }" > $CONFIGS_DIR/"$config_file"
;;
"ogDhcp")
config=$(yad --form --title="Configuración para $selected_component" \
--field="Configuración IP servidor de Boot" \
--field="Interfaces Boot" \
--width=400 --height=200 --center)
ogbootIP=$(echo "$config" | cut -d '|' -f 1)
interfaces=$(echo "$config" | cut -d '|' -f 2)
json_array_interfaces=$(echo "$interfaces" | jq -R 'split(",")')
echo "{\"ogbootIP\": \"$ogbootIP\", \"interfaces\": \"$json_array_interfaces\"}" > $CONFIGS_DIR/"$config_file"
;;
"ogBoot")
config=$(yad --form --title="Configuración para $selected_component" \
--field="ogCore Ip Server" \
--field="ogCore Server" \
--field="ogCore Dir" \
--field="ogBoot GitRepo" \
--field="ogBoot Samba User" \
--field="ogBoot Samba Pass" \
--width=400 --height=200 --center)
ogcore_ip=$(echo "$config" | cut -d '|' -f 1)
ogcore_server=$(echo "$config" | cut -d '|' -f 2)
ogcore_dir=$(echo "$config" | cut -d '|' -f 3)
ogboot_gitrepo=$(echo "$config" | cut -d '|' -f 4)
ogboot_samba_user=$(echo "$config" | cut -d '|' -f 5)
ogboot_samba_pass=$(echo "$config" | cut -d '|' -f 6)
echo "{\"ogcore_ip\": \"$ogcore_ip\", \"ogcore_server\": \"$ogcore_server\", \"ogcore_dir\": \"$ogcore_dir\", \"ogboot_gitrepo\": \"$ogboot_gitrepo\", \"ogboot_samba_user\": \"$ogboot_samba_user\", \"ogboot_samba_pass\": \"$ogboot_samba_pass\"}" > $CONFIGS_DIR/"$config_file"
;;
esac
# Verificar si los campos no están vacíos
# if [[ -z "$server_ip" || -z "$config_path" ]]; then
# yad --error --text="Debes proporcionar la IP del servidor y la ruta del fichero para $selected_component." --center
# exit 1
# fi
# Guardar la configuración en un archivo (cada componente tiene su archivo JSON)
config_file="./${selected_component}_config.json"
echo "{\"server_ip\": \"$server_ip\", \"config_path\": \"$config_path\"}" > "$config_file"
# Mostrar un mensaje de éxito
yad --info --text="Configuración guardada en $config_file para $selected_component." --center
done
# # Una vez se ha configurado todo, se puede proceder a la instalación de los componentes
# # Ejecutar la instalación con calamares y enviar el log a un archivo
# #calamares > installer.log 2>&1 & disown
sudo calamares > installer.log 2>&1