lunes, 3 de noviembre de 2025

WEBPROPv2 (XVII). Tareas de manternimiento (II). Clave SSH. Scripts de mantenimiento (copiar ficheros, arrancar servicios etc)

1. Clave ssh

1.1 Creación de una clave ssh para conectarnos al servidor remoto

Primeramente crearemos una carpeta (aunque no esnecesario). Utilizaremsos el tipo ed25519.  

Se le dará el nombre que queramos en este caso "srv01_ssh_key" y opcionalmente se puede añadir un comentario que se añadirá dicho comentario al final en el fichero de la clave pública. El comentario es: "Clave Srv01"


mkdir -p /home/myuser/keys

ssh-keygen -t ed25519 -f /home/myuser/keys/srv01_ssh_key -C "Clave Srv01"

Se creará una clave privada (srv01_ssh_key) y una clave pública (srv01_ssh_key.pub). Hay que tener cuidado de no compartir la clave privada.

Durante el proceso te pide una contraseña, que debes guardar

1.2. Uso de la clave ssh

Cada vez que ejecutemos un comando por ejemplo

ssh -i /home/myuser/keys/srv01_ssh_key usuario@servidor.com

Nos pedirá la contraseña.

Si queremos conservar la contraseña en memoria en cada conexión :

eval "$(ssh-agent -s)"
ssh-add /home/myuser/keys/srv01_ssh_key


1.3. Copiar la clave pública al servidor remoto

Se puede copiar utilizando ssh-copy-id o cat + ssh

# Si se dispone de ssh-copu-id
ssh-copy-id -i /home/myuser/keys/srv01_ssh_key usuario_remoto@IP_REMOTA

#Sinó
cat /home/myuser/keys/srv01_ssh_key | ssh usuario_remoto@IP_REMOTA "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"


1.4. Ejecutar en el servidor remoto

Podemos ejecutar ssh, scp o rsync usando esta clave:

# 1. ssh
ssh -i /home/myuser/keys/srv01_ssh_key usuario_remoto@IP_REMOTA

# 2. scp
scp -i /home/myuser/keys/srv01_ssh_key archivo usuario_remoto@IP_REMOTA:/ruta/

# 3. rsync
rsync -e "ssh -i /home/myuser/keys/srv01_ssh_key" ...

1.5. Configurar ~/.ssh/config con alias y ruta de la clave par acceder al servidor remoto

Así podemos evitar usar la opción -i. Veamos el fichero ~/.ssh/config:

# Read more about SSH config files: https://linux.die.net/man/5/ssh_config

# Servidor GLI i ara té la web de l'Ajuntament
Host srv01
    HostName IP_REMOTA
    User usuario_remoto
    IdentityFile /home/myuser/keys/srv01_ssh_key
    IdentitiesOnly yes
    Port 22

Y ahora ejecutando

ssh IP_REMOTA 

nos pediará la contraseña de la clave privada y entramos


2. Script de borrar (mover) logs y arrancar servicios remotos. Se ejecuta directamente sobre el servidor remoto

Queremos poder ejecutar los comandos mv, systemctl stop y systemctl restart remotamente, cosa que nos pide que lo hagamos con sudo. Para que no nos pida la contraseña de "sudo" ejecutamos:

sudo visudo

y metemos esta línea

usuario_remoto ALL=(ALL) NOPASSWD: /bin/mv, /bin/systemctl stop *, /bin/systemctl restart *


Con esto ya no nos pedirá la contraseña en estos 3 comandos para el usuario en cuestión.

Después de mucho preguntar al chatgpt, me ha dado muchos problemas el intentar parar servicios remotos. Es por ello que he decidido entrar en una sesión remota y ejecutar directamente esta shell que llama a las demas (una para parar los servicios, otra para mover los logs y otra para rearrancar los servicios)

Ahora, aunque lo ejecutemos en el servidor remot directamente no nos pedirá la contraseña de sudo.

2.1 script de llamada a las demás


SERV.00.sh

#!/bin/bash

# 1. Obtenemos la ruta de la carpeta donde está el script
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# 2. Lista de scripts a ejecutar
scripts=(
  "SERV01.Pas1.parar_serveis.sh"
  "SERV02.Pas2.copiar_y_borrar_logs.07.sh"
  "SERV03.Pas3.arrancar_serveis.01.sh"
)

# 3. Ejecución de lso scripts
for s in "${scripts[@]}"; do
  path="$DIR/$s"
  if [[ -f "$path" ]]; then
    echo "Ejecutando $s..."
    bash "$path" || { echo "Error en $s"; exit 1; }
  else
    echo "No encontrado: $path"
  fi
done

2.2 script de parada de los servicios


SERV01.Pas1.parar_serveis.sh
 
#!/bin/bash

# ==============================================================================
# CONFIGURACIÓN
# ==============================================================================

# Lista de patrones de búsqueda (expresiones regulares)
PATRONES_BUSQUEDA="(openr|python_)" 

# ==============================================================================
# PROCESO PRINCIPAL
# ==============================================================================

echo "--- Buscando y rearrancando servicios que coincidan con $PATRONES_BUSQUEDA ---"
echo "--------------------------------------------------------------------------"

# 1. Obtener la lista de servicios activos que coinciden con los patrones.
SERVICIOS=$(systemctl list-unit-files --type=service | grep -E "$PATRONES_BUSQUEDA" | awk '{print $1}')

# 2. Iterar sobre la lista de servicios y reiniciarlos.
if [ -z "$SERVICIOS" ]; then
    echo "ℹ️ No se encontraron servicios activos que comiencen por 'openr' o 'python_'."
else
    for SERVICIO in $SERVICIOS; do
        echo "➡️ Parando servicio: $SERVICIO"
        
        # Ejecutar el comando de reinicio. Se usa 'sudo' porque 'systemctl restart'
        # requiere permisos de superusuario para la mayoría de los servicios.
        sudo systemctl stop "$SERVICIO"
        
        # 3. Verificar el estado (opcional, pero recomendado)
        if [ $? -eq 0 ]; then
            echo "   ✅ $SERVICIO parado con éxito."
        else
            echo "   ❌ Fallo al parar $SERVICIO. Verifique los logs (journalctl)."
        fi
    done
fi

echo "--------------------------------------------------------------------------"
echo "--- Proceso completado ---"

2.3 script de traslado de ficheros logs 


SERV02.Pas2.copiar_y_borrar_logs.07.sh
 
#!/bin/bash

#################################
# DEFINICION DE PARAMETROS
# Ruta al fichero de configuración (puedes modificarla)
#################################
# a. Configuración de openresty
FICHERO="/usr/local/openresty/nginx/conf/nginx.conf"

# b. Define your service name pattern (optional)
SERVICES_BEGIN_WITH='^(openr|python_)'

# c. destino de los logs al copiarse
DESTINO="/home/informatica/mylogs"
#========================================
#  Función: copiar_con_fecha
#  Mueve un fichero al destino, añadiendo timestamp antes de la extensión
#========================================



mover_fichero_con_fecha() {
    local fichero="$1"
    #local destino="/home/informatica/mylogs"

    # Verificar si existe el fichero
    if [[ ! -f "$fichero" ]]; then
        echo "❌ No existe el fichero: $fichero"
        return 1
    fi

    # Crear destino si no existe
    mkdir -p "$DESTINO"

    # Obtener nombre base y extensión
    local nombre_base
    nombre_base=$(basename "$fichero")
    local nombre_sin_ext="${nombre_base%.*}"
    local extension="${nombre_base##*.}"

    # Si no tiene extensión
    if [[ "$nombre_sin_ext" == "$nombre_base" ]]; then
        extension=""
    fi

    # Fecha y hora actual
    local timestamp
    timestamp=$(date +"%Y%m%d_%H%M%S")

    # Crear nuevo nombre con timestamp
    local nuevo_nombre
    if [[ -n "$extension" ]]; then
        nuevo_nombre="${nombre_sin_ext}_${timestamp}.${extension}"
    else
        nuevo_nombre="${nombre_sin_ext}_${timestamp}"
    fi

    # Mover el fichero (usar sudo si no hay permisos)
    if [[ -w "$(dirname "$fichero")" ]]; then
        mv "$fichero" "$DESTINO/$nuevo_nombre"
    else
        echo "Using sudo to move $fichero"
        sudo mv "$fichero" "$DESTINO/$nuevo_nombre"
    fi

    echo "✅ Movido a: $DESTINO/$nuevo_nombre"
}



#========================================
#1. Buscamos los logs de openresty
#========================================
# Ruta al fichero de configuración (puedes modificarla)
#FICHERO="/usr/local/openresty/nginx/conf/nginx.conf"

# Verificar si el fichero existe
if [[ ! -f "$FICHERO" ]]; then
    echo "❌ El fichero no existe: $FICHERO"
    exit 1
fi

# Buscar la línea que contiene "error_log /usr/" y extraer la palabra con "/usr/"
all_log_paths=$(grep "error_log /usr/" "$FICHERO" | grep -oE '[^[:space:]]*/usr/[^[:space:];]*')
all_log_paths+=$'\n'"${all_log_paths/error.log/access.log}"

echo "$all_log_paths"

#==================================================================
#2. Añdimos los logs de los servicios que comienzan por "python_"
#==================================================================

# Define your service name pattern (optional)
#SERVICES_BEGIN_WITH='^(openr|python_)'

# Iterate over matching services
for service in $(systemctl list-unit-files --type=service | awk '{print $1}' | grep -E "$SERVICES_BEGIN_WITH"); do
    echo "Service: $service"

    # Extract relevant lines
    current_logs=$(systemctl cat "$service" 2>/dev/null | \
                   grep -E '(StandardOutput|StandardError|SyslogIdentifier)' )

    if [ -n "$current_logs" ]; then
        echo "Current_logs: $current_logs"

		for log_file in $current_logs; do
			# Append only the extracted values (after ":")
			#echo "log_file: $log_file"
			all_log_paths+=$'\n'"${log_file#*:}"
		done	
    fi

    #echo "Journal logs: journalctl -u $service"
    #echo "---"
done

echo "$all_log_paths"

#==================================================================
#3.Movemos los ficheros a la carpeta $DESTINO
#==================================================================
for fichero in $all_log_paths; do if [[ -n "$fichero" && -e "$fichero" ]]; then #if [[ -w "$fichero" ]]; then # rm -f "$fichero" #else # echo "Using sudo to remove $fichero" # sudo rm -f "$fichero" mover_fichero_con_fecha "$fichero" else echo "File not found or variable empty: $fichero" fi done

2.4 script para rearrancar los servicios


SERV03.Pas3.arrancar_serveis.01.sh

#!/bin/bash

# ==============================================================================
# CONFIGURACIÓN
# ==============================================================================

# Lista de patrones de búsqueda (expresiones regulares)
PATRONES_BUSQUEDA="(openr|python_)" 

# ==============================================================================
# PROCESO PRINCIPAL
# ==============================================================================

echo "--- Buscando y rearrancando servicios que coincidan con $PATRONES_BUSQUEDA ---"
echo "--------------------------------------------------------------------------"

# 1. Obtener la lista de servicios activos que coinciden con los patrones.
SERVICIOS=$(systemctl list-unit-files --type=service | grep -E "$PATRONES_BUSQUEDA" | awk '{print $1}')
echo "$SERVICIOS"
# 2. Iterar sobre la lista de servicios y reiniciarlos.
if [ -z "$SERVICIOS" ]; then
    echo "ℹ️ No se encontraron servicios activos que comiencen por 'openr' o 'python_'."
else
    for SERVICIO in $SERVICIOS; do
        echo "➡️ Rearrancando servicio: $SERVICIO"
        
        # Ejecutar el comando de reinicio. Se usa 'sudo' porque 'systemctl restart'
        # requiere permisos de superusuario para la mayoría de los servicios.
        sudo systemctl restart "$SERVICIO"
        
        # 3. Verificar el estado (opcional, pero recomendado)
        if [ $? -eq 0 ]; then
            echo "   ✅ $SERVICIO rearrancado con éxito."
        else
            echo "   ❌ Fallo al rearrancar $SERVICIO. Verifique los logs (journalctl)."
        fi
    done
fi

echo "--------------------------------------------------------------------------"
echo "--- Proceso completado ---"



3. Script que se ejecuta en cliente para copiar los programas al servidor

Hay que tener en cuenta que para poder ejecutar este script tenemos que entrar en sudo visudo y asegurarnos que tiene la línea del usuario para que no pida la contraseña en el comando mv.

CLI00.TOT.Copiar_a_WEBPROP.sh

#!/usr/bin/env bash
# ============================================================
# Copiar TODAS las subcarpetas de un directorio al servidor remoto 192.168.28.16
# EXCEPTO algunas, usando usuario, contraseña e IP (sin clave SSH)
# ============================================================

# Uso:
# ./Copiar_a_WEBPROP.sh /carpeta/local /ruta/remota IP usuario contraseña "carpeta1 carpeta2 ..." [puerto]
# Ejemplo:
# ./copiar_subcarpetas_excluyendo.sh /home/eduard/proyectos /home/juan/backups 192.168.1.100 juan MiPass123 "temp logs node_modules" 22

LOCAL_DIR="/home/ximo/MyPython/versio.02/softprop"
REMOTE_DIR="/home/informatica/eduApps/softprop"
REMOTE_IP="192.XXX.XXX.XXX"
REMOTE_USER="miusuario"
REMOTE_PASS="mipassword"
EXCLUDE_LIST=".pytest_cache .vscode 01_provetes utilitats_varies"    # lista separada por espacios
REMOTE_PORT="${7:-22}"


# Definimos una función para copiar entre carpetas remotas
# ==========================================
# FUNCION PARA COPIAR FICHEROS ENTRE CARPETAS REMOTAS
# ==========================================
copiar_remoto() {
    local origen="$1"
    local destino="$2"

    echo "➡️  Copiando en remoto: $origen$destino"

    # Ejecuta copia remota vía SSH
    #sshpass -p "$REMOTE_PASS" ssh -o StrictHostKeyChecking=no "$REMOTE_USER@$REMOTE_HOST" \
    #    "sudo cp -f \"$origen\" \"$destino\" && echo '✅ Copiado: $destino' || echo '❌ Error copiando $origen'"

	sshpass -p "$REMOTE_PASS" ssh -T "$REMOTE_USER@$REMOTE_IP" "sudo cp -f \"$origen\" \"$destino\" && echo '✅ Copiado: $destino' || echo '❌ Error copiando $origen'"
}




# Verifica parámetros
#if [[ -z "$LOCAL_DIR" || -z "$REMOTE_DIR" || -z "$REMOTE_IP" || -z "$REMOTE_USER" || -z "$REMOTE_PASS" ]]; then
#    echo "Uso: $0 /carpeta/local /ruta/remota IP usuario contraseña \"excluir1 excluir2 ...\" [puerto]"
#    exit 1
#fi

# Comprueba dependencias
for cmd in sshpass rsync; do
    if ! command -v $cmd >/dev/null 2>&1; then
        echo "Instalando $cmd..."
        sudo apt update && sudo apt install -y $cmd
    fi
done

# Construye lista de exclusiones para rsync
EXCLUDE_ARGS=()
if [[ -n "$EXCLUDE_LIST" ]]; then
    for folder in $EXCLUDE_LIST; do
        EXCLUDE_ARGS+=(--exclude="$folder/")
    done
fi

echo "🚀 Iniciando copia desde $LOCAL_DIR$REMOTE_USER@$REMOTE_IP:$REMOTE_DIR"
echo "Excluyendo: $EXCLUDE_LIST"
echo "==========================================================="

# Copia todas las subcarpetas excepto las excluidas
sshpass -p "$REMOTE_PASS" rsync -avz --progress \
    -e "ssh -p $REMOTE_PORT -o StrictHostKeyChecking=no" \
    "${EXCLUDE_ARGS[@]}" \
    "$LOCAL_DIR/" "${REMOTE_USER}@${REMOTE_IP}:$REMOTE_DIR"

if [[ $? -eq 0 ]]; then
    echo "✅ Copia completada correctamente."
else
    echo "❌ Error al copiar carpetas."
    exit 2
fi


# Ahora copiamos los ficheros a su lugar:
EXPORTACIO="$REMOTE_DIR/_exportacio"
copiar_remoto "$EXPORTACIO/authentication/xmopenresty.py" \
              "$REMOTE_DIR/authentication/xmopenresty.py"

copiar_remoto "$EXPORTACIO/menus/mnu_main.py" \
              "$REMOTE_DIR/menus/mnu_main.py"

copiar_remoto "$EXPORTACIO/openresty/v04/nginx.conf" \
              "/usr/local/openresty/nginx/conf/nginx.conf"

echo "✅ Todas las copias han finalizado."




martes, 28 de octubre de 2025

WEBPROPv2 (XVI). Solución de errores(3). Un registro de Osticket (id=769) da error al mostrarse. HTMX problem

1. Causa del problema

htmx cuando hace un post, si el elemento que lo realiza está dentro de un form entonces envia todos los datos del form en el request dentro de una estructura "FormData"

Si el form tiene muchso datos y pasamos for nginx (openresty), entonces hacemos un "overflow" del caché del mismo.

ChatGPT y Claude plantean soluciones QUE NO FUNCIONAN que es añadirle algunos de estos parámetros al htmx (aplicados a un botón o un anchor):

form = 'none' 
hx-include="none" 
hx-include="[data-include='never']"

Al final utilizando Claude lo que se hace es realizar un fetch, para simplificar creamos una función para utilizarla igual:

/**
 * Fetch data from the given endpoint and update the target element with the response HTML.
 * @param {*} endpoint 
 * @param {*} data 
 * @param {*} targetSelector 
 * @returns 
 */
async function fetchAndUpdate(endpoint, data, targetSelector) {
	console.log("executing fetchAndUpdate with:", endpoint, JSON.stringify(data), targetSelector);
    try {
        const response = await fetch(endpoint, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
        
        const html = await response.text();
        const target = document.querySelector(targetSelector);
        
        if (target) {
            target.innerHTML = html;
        } else {
            console.warn(`Target element "${targetSelector}" not found`);
        }
        
        return html;
    } catch (error) {
        console.error('Fetch error:', error);
        throw error;
    }
}

y luego la llamamos así:

// ✅ Define the "value" property
  set value(newValue) {
    
	const tab_id= sessionStorage.getItem("id_tab");
        const mydata= { sql_id: newValue, my_id: this._parentId, tab_id: tab_id || "" };		
	fetchAndUpdate( this.url, mydata, this.target );
  }



martes, 21 de octubre de 2025

WEBPROPv2 (XV). Actualizando versiones.

1. Comprobaciones previas:

a. Copiar programas desde el ordenador de desarrollo al de producción

Para ello utilizaremos el FileZilla (también sepuede utilizar ssh)

Se deberán copiar todos los ficheros y carpetas de la carpeta softprop de desarrollo excepto las carpetas:

  • .pythest_cache
  • .vscode
  • __pycache__
  • _provetes
  • camel2snake
  • venv_softprop
  • zz_copies_seg_zip
Si se hubiera tocado parte de la configuración de openresty como nginx.conf,algún modulo lua, certificados etcentonces se copiará:
  • openresty/v04/* a /usr/local/openresty/nginx/conf/

b. Copiar y revisar el contenido de _exportacio

Ahora hay que comprobar que las rutas a los servidores y entorno virtual python sean correctas en las versiones que hemos copiado y modificado adecuadamente en _exportació

1. En autentication/xmopenresty.py verifcar que está activa la parte remota y el entorno virtual remoto:

#!/home//informatica/eduApps/softprop/venv_softprop/bin/python3

#????????###################################################
# CANVIAR LOCAL:
#my_host = "192.168.XX.XX"
#my_port = 5001
#------------------------------
# CANVIAR REMOT:
my_host = "192.168.YY.YY"
my_port = 5001
####################################################

2. En menus/menu_main.py verificar lo mismo

#!/home/informatica/eduApps/softprop/venv_softprop/bin/python3

#????????###################################################
# CANVIAR LOCAL:
#my_host = "edu.poblacion.es"
#my_port = 5000
#------------------------------
# CANVIAR REMOT:
my_host = "proves.poblacion.es"
my_port = 5000
####################################################

Y en openresty/v04/nginx.conf verificar:

http {
    #????????============================================================================
	# CANVIAR LOCAL: 
	# --- Constants defined with map
    #map "" $MY_SERVER        { default 192.168.10.5; }
    #map "" $MY_SERVER_NAME   { default edu.tavernes.es; }
    #map "" $MY_AUTH_URL      { default https://192.168.10.5:5001/auth; }
    #map "" $MY_CONF_PATH     { default /usr/local/openresty/nginx/conf; }
    #map "" $SESSION_EXPIRATION { default 3600; }  # 1 hour
	# Creamos una bateria de servidores para softprop con un solo servidor
    #upstream softpropsrv {
	#	server 192.168.10.5:5000;
	#	keepalive 32; # NOU 2025-10-6 (2)
    #}   
	#-----------------------------------------
	#  CANVIAR REMOT!
	# --- Constants defined with map
    map "" $MY_SERVER        { default 192.168.YY.YY; }
    map "" $MY_SERVER_NAME   { default proves.localidad.es; }
    map "" $MY_AUTH_URL      { default https://192.168.YY.YY:5001/auth; }
    map "" $MY_CONF_PATH     { default /usr/local/openresty/nginx/conf; }
    map "" $SESSION_EXPIRATION { default 3600; }  # 1 hour
	# Creamos una bateria de servidores para softprop con un solo servidor
    upstream softpropsrv {
		server 192.168.YY.YY:5000;
		keepalive 32; # NOU 2025-10-6 (2)
    }   
	# fi: CANVIAR
	#============================================================================
	

Y se copiara desde el servidor de desarrollo al de producción
  • _exportacio/authentication/xmopenresty.py a authentication/xmopenresty.py
  • _exportacio/menus/menu_main.py a menuis/menu_main.py
  • _exportacio/openresty/v04/nginx.conf a /usr/local/openresty/nginx/conf/nginx.conf
Si ppor algun motivo los ficheros de servicios se hubieran perdido, en _exportacio/services se encuentran dichos ficheros

c. Rearrancar los siguientes servicios:

  1. openresty
  2. python_menus_mnu_main_py.service
  3. python_authentication_xmopenresty_py.service
Se muestran los comandos para rearrancar cada uno de ellos:

sudo systemctl restart openresty
sudo systemctl restart python_menus_mnu_main_py.service
sudo systemctl restart python_authentication_xmopenresty_py.service

Para ver el estado de cada uno uy ver si estń arrancados se ejecutará esta orden:

systemctl list-units --type=service | grep -E 'openresty|python'

Si no estuvieran definidos estos servicios, se consultará a esta entrada.





viernes, 17 de octubre de 2025

WEBPROPv2 (XIV). Ampliar las funcionalidades. Instalar libreoffice (y unoconv ->No)

 1. Introducción

Para poder visualizar ficheros se requiere tener un conversor de ficheros doc, docx, xls ,odt ... a html para poderlos visualizar por tanto se requiere que:

  1. Se instale unoconv y libreoffice
  2. Crear el servicio de libreoffice para que sea mas rápida la conversión
  3. Ejecutar el servicio
Para instalr estos ficheros en Ubuntu (ojo la opción --fix-missing es por si no puede descargar algun paquete):

sudo apt update --fix-missing
sudo apt install unoconv libreoffice --fix-missing

Y para crear  el servicio:

Ejecutamos:

sudo nano /etc/systemd/system/libreoffice-listener.service


Y le damos este contenido:

[Unit]
Description=LibreOffice headless listener for unoconv
After=network.target

[Service]
Type=simple
ExecStart=/usr/bin/libreoffice --headless --nologo --nofirststartwizard  --norestore --accept="socket,host=127.0.0.1,port=2002;urp;"
Restart=always
RestartSec=5

# Opcional: ejecutar como un usuario no root
User=libreoffice
Environment="HOME=/tmp"

[Install]
WantedBy=multi-user.target


Y como lo ejecutamos como el usuario libreoffice, tenemos que crearlo:

sudo useradd -r -s /bin/false libreoffice
sudo mkdir /tmp/libreoffice
sudo chown libreoffice:libreoffice /tmp/libreoffice

Ahora toca arrancar el servicio:

sudo systemctl daemon-reload
sudo systemctl enable --now libreoffice-listener.service


Y verificamos que esté ctivo el servicio:

systemctl status libreoffice-listener.service

Y que funcione la conversión:

unoconv -f html -d document test.docx


que crea el fichero test,html al convertir el documento (existente) text.docx


2. Otros recursos necesarios.
Para que libeoffice funcione bien hay que instalar algunos programas en el servidor:







lunes, 13 de octubre de 2025

WEBPROPv2 (XIII). Tareas de manternimiento (I). Instalar librerías.

 1. Instalar nuevas dependencias en el entorno virtual

Primeramente habrá que saber que dependencias instalar. Para ello nos vamos al entorno virtual de la máquina de desarrollo y creamos el fichero "requirements.txt"

1
2
3
4
5
6
7
8
# 1. Primeramente vamos a la carpeta que contiene la carpeta del entorno virtual
cd softprop

#2. Activamos el entrono que esta en nuestro caso en venv_softprop
source venv_softprop/bin/activate

#3. Creamos el fichero de la lista de librerías
pip freeze > requirements.txt


Ahora hay que copiar el fichero "requirements.txt" en el host remoto de produccón a la carpeta padre que contiene la carpeta del entrono virtual. En mi caso lo he hecho con Filezilla

Nos conectamos con ssh con el servidor remoto de producción y hacemos la misma operación pero al revés:

1
2
3
4
5
6
7
8
# 1. Primeramente vamos a la carpeta que contiene la carpeta del entorno virtual
cd softprop

#2. Activamos el entrono que esta en nuestro caso en venv_softprop
source venv_softprop/bin/activate

#3. Creamos el fichero de la lista de librerías
pip install -r requirements.txt


Si queremos actualizar todas las librerías del entorno virtual:

pip list --outdated --format=columns | tail -n +3 | awk '{print $1}' | xargs -n1 pip install -U




 


domingo, 12 de octubre de 2025

COPIAR CARPETAS pero evitando duplicados internos y excluyendo algunas carpetas y extensiones de ficheros

 Introducción

Lo vamos a hacer en 2 pasos:

  1. Detectaremos duplicados por nombre y tamaño (excluyendo algunas carpetas y extensiones de ficheros) creando un fichero con la lista de duplicados
  2. Copiaremos los ficheros excluyendo los ficheros de la lista guadada en el fichero generado anteriormente y las carpetas y ficheros excluidos por extensión

1. Generar una lista de ficheros duplicados:


  • Las líneas 35 a 39 indican las carpetas a excluir
  • Las lÍneas 42 a 44 indica las extensiones de ficheros a excluir
  • Al final nos da el nombre del fichero generado
  • Para ejecutar el script hay que pasarle la carpeta a analizar
Ejecutamos este  script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#!/bin/bash
# buscar_duplicados_exacto_final.sh
# Autor: Ximo Dante con IA
# Busca archivos con el mismo nombre y tamaño exacto,
# excluyendo carpetas del sistema y archivos temporales.
# Marca con "#" las cabeceras y el fichero más superficial.

set -euo pipefail
IFS=$'\n'

BASE_DIR="${1:-}"

if [[ -z "$BASE_DIR" ]]; then
  echo "❌ Uso: $0 /ruta/a/carpeta"
  exit 1
fi
if [[ ! -d "$BASE_DIR" ]]; then
  echo "❌ No existe el directorio: $BASE_DIR"
  exit 1
fi

TIMESTAMP=$(date +%Y%m%d_%H%M%S)
OUT="duplicados_final_${TIMESTAMP}.txt"

echo "🔍 Buscando duplicados exactos en: $BASE_DIR"
echo "📝 Informe: $OUT"
echo

# ------------------------------------------------------------
# 1️⃣ Buscar y procesar con find + awk (sin bucles)
# ------------------------------------------------------------
find "$BASE_DIR" \
  \( -type d \( \
      -name ".*" -o -name "\$*" \
      -o -iname "Archivos de programa" -o -iname "Descargas" -o -iname "Downloads" \
      -o -iname "Documents and Settings" -o -iname "Logs" -o -iname "PerfLogs" \
      -o -iname "Program Files" -o -iname "Program Files (x86)" -o -iname "ProgramData" \
      -o -iname "Recovery" -o -iname "System Volume Information" -o -iname "Temp" \
      -o -iname "Users" -o -iname "Windows" -o -iname "Windows10Upgrade" \
    \) -prune \) \
  -o -type f \
     ! -iname "*.exe" ! -iname "*.log" ! -iname "*.dll" ! -iname "*.sys"\
     ! -iname "*.part" ! -iname "*.crdownload" ! -iname "*.tmp" \
     ! -iname "*.partial" ! -iname "*.download" ! -iname "*!qB*" ! -iname "*.aria2" \
  -printf '%f\t%s\t%p\n' 2>/dev/null \
| sort -k1,1 -k2,2n \
| awk -F'\t' -v base="$BASE_DIR" '
  function depth(path,   nf, nb, a, b) {
    nf = split(path, a, "/");
    nb = split(base, b, "/");
    return nf - nb - 1;
  }

  {
    name=$1; size=$2; path=$3;
    # Ignorar temporales (~$, _~, ~.)
    if (name ~ /^~[$]|^_~|^~\./) next;
    key = name "|" size;
    files[key] = files[key] ? files[key] RS path : path;
  }
  END {
    dup_groups=0;
    for (k in files) {
      n = split(files[k], arr, "\n");
      if (n > 1) {
        dup_groups++;
        split(k, parts, "|");
        print "#-----------------------------------";
        print "#📂 Duplicado exacto: " parts[1] " (" parts[2] " bytes)";
        # Fichero más superficial (menor profundidad)
        min_d=1e9; idx=0;
        for (i=1; i<=n; i++) {
          d = depth(arr[i]);
          depths[i]=d;
          if (d < min_d) { min_d=d; idx=i; }
        }
        for (i=1; i<=n; i++) {
          mark = (i==idx) ? "#" : " ";
          printf "%s%s [nivel=%d]\n", mark, arr[i], depths[i];
        }
      }
    }
    if (dup_groups==0)
      print "ℹ️ No se encontraron duplicados exactos (nombre + tamaño)" > "/dev/stderr";
    else
      print "✅ Se encontraron " dup_groups " grupos de duplicados." > "/dev/stderr";
  }
' > "$OUT"

echo
if [[ -s "$OUT" ]]; then
  echo "✅ Informe generado: $(realpath "$OUT")"
else
  echo "ℹ️ El fichero está vacío: no se detectaron duplicados exactos tras exclusiones."
fi
echo


2. Copia de carpetas en base al fichero de duplicados:

  • Las líneas 55 a 59 indican las carpetas a excluir
  • Las lÍneas 62 a 64 indica las extensiones de ficheros a excluir
  • Al final nos da el nombre del fichero generado con la lista de ficheros copiados
  • Para ejecutar el script hay que pasarle las carpeta de origen y destino y la ruta del fichero de duplicados generados en el punto anterior
Veamos el script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#!/bin/bash
# ============================================================
# 🧰 copiar_no_duplicados.sh
# Autor: Ximo Dante + IA
# Copia solo los ficheros no duplicados ni excluidos
# según el informe generado por buscar_duplicados_exacto_final.sh
# Sin usar rsync — usa find + cp --parents (más estable)
# ============================================================

set -euo pipefail
IFS=$'\n'

ORIGEN="${1:-}"
DESTINO="${2:-}"
DUP_FILE="${3:-}"

if [[ -z "$ORIGEN" || -z "$DESTINO" || -z "$DUP_FILE" ]]; then
  echo "❌ Uso: $0 /carpeta/origen /carpeta/destino duplicados_final_xxx.txt"
  exit 1
fi

if [[ ! -d "$ORIGEN" ]]; then
  echo "❌ Carpeta origen no existe: $ORIGEN"
  exit 1
fi
if [[ ! -f "$DUP_FILE" ]]; then
  echo "❌ No existe el fichero de duplicados: $DUP_FILE"
  exit 1
fi

mkdir -p "$DESTINO"
TS=$(date +%Y%m%d_%H%M%S)
LOG="$DESTINO/copiar_no_duplicados_${TS}.log"

echo "📂 Carpeta origen: $ORIGEN"
echo "📁 Carpeta destino: $DESTINO"
echo "🧾 Archivo de duplicados: $DUP_FILE"
echo "📝 Log: $LOG"
echo

# ------------------------------------------------------------
# 1️⃣ Construir lista de rutas a excluir
# ------------------------------------------------------------
EXCL_TMP=$(mktemp)
grep -v '^#' "$DUP_FILE" | grep -E '^/' > "$EXCL_TMP" || true

# ------------------------------------------------------------
# 2️⃣ Encontrar archivos válidos (no duplicados ni excluidos)
# ------------------------------------------------------------
echo "🔎 Escaneando archivos válidos..."

find "$ORIGEN" \
  \( -type d \( \
      -name ".*" -o -name "\$*" \
      -o -iname "Archivos de programa" -o -iname "Descargas" -o -iname "Downloads" \
      -o -iname "Documents and Settings" -o -iname "Logs" -o -iname "PerfLogs" \
      -o -iname "Program Files" -o -iname "Program Files (x86)" -o -iname "ProgramData" \
      -o -iname "Recovery" -o -iname "System Volume Information" -o -iname "Temp" \
      -o -iname "Users" -o -iname "Windows" -o -iname "Windows10Upgrade" \
    \) -prune \) \
  -o -type f \
     ! -iname "*.exe" ! -iname "*.log" ! -iname "*.dll" ! -iname "*.sys" \
     ! -iname "*.part" ! -iname "*.crdownload" ! -iname "*.tmp" \
     ! -iname "*.partial" ! -iname "*.download" ! -iname "*!qB*" ! -iname "*.aria2" \
  -print0 2>/dev/null \
| grep -zavFf "$EXCL_TMP" \
| tee >(xargs -0 -I{} bash -c '
      FILE="{}"
      REL="${FILE#'"$ORIGEN"'/}"
      DEST_FILE="'"$DESTINO"'/$REL"
      mkdir -p "$(dirname "$DEST_FILE")"
      cp -p "$FILE" "$DEST_FILE"
      echo "✔ Copiado: $REL" >> "'"$LOG"'"
  ') > /dev/null

# ------------------------------------------------------------
# 3️⃣ Mostrar resumen
# ------------------------------------------------------------
COPIADOS=$(grep -c "✔ Copiado:" "$LOG" || echo 0)
echo
echo "✅ Copia completada."
echo "📦 Archivos copiados: $COPIADOS"
echo "📄 Log detallado: $LOG"
echo