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. 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




sábado, 11 de octubre de 2025

WEBPROPv2 (XII). Mostrar ficheros en un control FastHTML

 Veamos el código que ejecuta en python  un uvicorn y muestra el fichero que le decimos:

El truco está en utilizar libreoffice para transformar los ficheros de ofimática ( ods,odt, xls,xlsx, doc,docx ..) a html y mostrar el html generado.

Si el fichero es grande puede enlentecer mucho la carga, conversión y muestra

import base64
import mimetypes
from fasthtml import common as fh
import uvicorn
from pathlib import Path
import subprocess
import shutil


app = fh.FastHTML()



def convertir_a_html(path: Path) -> Path | None:
    """Convierte el archivo a HTML usando LibreOffice y devuelve la ruta resultante."""
    output_dir = Path("/tmp")
    result = subprocess.run(
        ["libreoffice", "--headless", "--convert-to", "html", str(path), "--outdir", str(output_dir)],
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )
    if result.returncode == 0:
        html_file = output_dir / (path.stem + ".html")
        if html_file.exists():
            return html_file
    return None

def generar_viewer(path: Path):
    mime_type, _ = mimetypes.guess_type(str(path))
    if not mime_type:
        mime_type = "application/octet-stream"

    data = path.read_bytes()
    b64 = base64.b64encode(data).decode("utf-8")

    # Imágenes
    if mime_type.startswith("image/"):
        return fh.Img(src=f"data:{mime_type};base64,{b64}", cls="img-fluid rounded shadow", alt=path.name)

    # PDF
    if mime_type == "application/pdf":
        return fh.Iframe(src=f"data:{mime_type};base64,{b64}",
                         style="width:100%; height:90vh;",
                         cls="border rounded shadow-sm",
                         title=path.name)

    # Texto
    if mime_type.startswith("text/"):
        content = data.decode("utf-8", errors="ignore")
        return fh.Pre(content, cls="bg-light p-3 border rounded")

    # ODS / XLSX / DOCX — convertir a HTML
    convertible_types = {
        "application/vnd.oasis.opendocument.spreadsheet",
        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "application/vnd.oasis.opendocument.text",
        'application/msword',
        'application/vnd.ms-excel',
    }
    if mime_type in convertible_types:
        html_file = convertir_a_html(path)
        if html_file:
            html_content = html_file.read_text(encoding="utf-8", errors="ignore")
            return fh.Div(fh.NotStr(html_content), cls="border p-2 bg-white shadow-sm")

    # Otros: ofrecer descarga
    return fh.Div(
        fh.P("El archivo no puede mostrarse directamente en el navegador.", cls="text-muted"),
        fh.A("Descargar archivo", href=f"data:{mime_type};base64,{b64}",
             download=path.name, cls="btn btn-primary mt-3"),
        cls="text-center"
    )

@app.get("/mostrarfichero")
async def mostrar_fichero(req):
    path_param = req.query_params.get("pathfichero")
    if not path_param:
        return fh.Html(fh.Body(fh.H4("⚠️ Falta el parámetro 'pathfichero'")))
    path = Path(path_param)
    if not path.exists():
        return fh.Html(fh.Body(fh.H4(f"❌ Archivo no encontrado: {path_param}")))
    viewer = generar_viewer(path)
    return fh.Html(
        fh.Head(fh.Link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css")),
        fh.Body(
            fh.Div(
                fh.H3(f"Mostrando: {path.name}", cls="text-center my-4"),
                viewer,
                cls="container"
            )
        )
    )

if __name__ == "__main__":
    print("🚀 Abre en el navegador: http://localhost:8000/mostrarfichero?pathfichero=/home/edu/kk/ODS01.ods")
    uvicorn.run(app, host="0.0.0.0", port=8000)


Y para ejecutarlo, tal como se dice en as últimas sentencias, ejecutamos el programa python desde visual studio code y en un navegador apuntamos a esa ruta donde en /home/kk/OSD01.ods está un fichero de hoja de cálculo

http://localhost:8000/mostrarfichero?pathfichero=/home/edu/kk/ODS01.ods


martes, 7 de octubre de 2025

WEBPROPv2 (XI). Arranque de los servicios de la aplicacion y validación de usuarios. Consulta de los logs

1. Creación de los servicios del programa y autenticación

Tal como vimos en el apartado anterior de solución de errores, es mejor arrancar estas aplicaciones como servicios, así se pueden reactivar sin problemas.

También se vió como se debe arrancar uvicorn para que pueda tener varios workers en marcha y así no bloquee su ejecución a otros usuarios si está ejecutando una tarea larga como la de captura de decretos.

Veamos los ficheros de servicio que se guardan en /etc/systemd/system

python_menus_mnu_main_py.service:

[Unit]
Description=Softprop Menu Main Python Service
After=network.target

[Service]
# Usuario y grupo que ejecutarán el servicio
User=informatica
Group=informatica

# Directorio de trabajo base
WorkingDirectory=/home/informatica/eduApps

# Activar entorno virtual
#Environment="PATH=/home/informatica/eduApps/softprop/venv_softprop/bin"
Environment="PATH=/home/informatica/eduApps/venv_softprop/bin"
# Ejecutar directamente el script (usa el shebang) SOLO USA UN WORKER #ExecStart=/home/informatica/eduApps/softprop/menus/mnu_main.py
#ExecStart=/home/informatica/eduApps/venv_softprop/bin/uvicorn softprop.menus.mnu_main:app
# Ejecutar uvicorn directamente (importa el módulo mnu_main:app) ExecStart=/home/informatica/eduApps/venv_softprop/bin/uvicorn softprop.menus.mnu_main:app \ --host proves.tavernes.es \ --port 5000 \ --workers 4 \ --ssl-keyfile /home/informatica/eduApps/softprop/static/certs/wildcard.municipio.es.key \ --ssl-certfile /home/informatica/eduApps/softprop/static/certs/wildcard.municipio.es.crt


ExecStart=/usr/bin/python3 /ruta/mi_programa.py >> /var/log/mi_servicio.log 2>&1


# Reinicio automático en caso de error
Restart=always
RestartSec=5

# Redirección de logs
StandardOutput=append:/var/log/softprop_menus_mnu_main.log
StandardError=append:/var/log/softprop_menus_mnu_main.err

[Install]
WantedBy=multi-user.target


y para el servicio de autenticación python_authentication_xmopenresty_py.service:



[Unit]
Description=Softprop OpenResty Python Service After=network.target
[Service]
# Usuario y grupo del servicio User=informatica Group=informatica # Directorio base del proyecto WorkingDirectory=/home/informatica/eduApps # Activar entorno virtual
#Environment="PATH=/home/informatica/eduApps/softprop/venv_softprop/bin"
Environment="PATH=/home/informatica/eduApps/venv_softprop/bin"

#ExecStart=/home/informatica/eduApps/softprop/authentication/xmopenresty.py # Ejecutar uvicorn directamente importando el módulo
#ExecStart=/home/informatica/eduApps/softprop/venv_softprop/bin/uvicorn softprop.authentication.xmopenresty:app 
ExecStart=/home/informatica/eduApps/venv_softprop/bin/uvicorn softprop.authentication.xmopenresty:app \
  --host 192.168.28.16 \
  --port 5001 \
  --workers 4 \
  --ssl-keyfile /home/informatica/eduApps/softprop/static/certs/wildcard.municipio.es.key \
  --ssl-certfile /home/informatica/eduApps/softprop/static/certs/wildcard.municipio.es.crt

# Reiniciar automáticamente en caso de error
Restart=always
RestartSec=5

#Logs
StandardOutput=append:/var/log/softprop_authentication_openresty.log
StandardError=append:/var/log/softprop_authentication_openresty.err

[Install]
WantedBy=multi-user.target

Ahora hay que hacer estas tareas:

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

y sobre todo controlar los LOGS !! que crecen mucho

/var/log/softprop_menus_mnu_main.log
/var/log/softprop_menus_mnu_main.err
/var/log/softprop_authentication_openresty.log
/var/log/softprop_authentication_openresty.err

2. Consulta de los logs

A parte de los 4 logs indicados arriba, los "print" tienen su salida manejada por el "systemd", y àra consultarlos se haria:

sudo journalctl -u python_menus_mnu_main_py.service -f
sudo journalctl -u python_authentication_xmopenresty_py.service -f