From 874fe0c46fb058b077d5fcae574b0ae00da60153 Mon Sep 17 00:00:00 2001 From: hreyes Date: Thu, 30 Jul 2026 16:16:28 -0600 Subject: [PATCH] =?UTF-8?q?fix(install):=20devolver=20la=20propiedad=20del?= =?UTF-8?q?=20=C3=A1rbol=20al=20usuario=20del=20servicio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instalando con sudo, el agente quedaba sin poder leer ni escribir NADA de lo suyo, y tanto el panel como su sonda lo reportaban como éxito. El bootstrap ejecuta el binario como root, y ese arranque crea todo el árbol bajo PREFIX: config/, config/data/app.db, config/logs/, Entrada/, Procesados/, Fallados/ y Temp/. La siembra escribe config/.env en 0600, también de root. Pero el unit se registra con User=$SUDO_USER, así que el agente arranca como una cuenta común que no puede leer su configuración (load_dotenv sin try/except), ni guardar jobs en su base, ni mover ZIPs entre las carpetas de trabajo. install.sh no hacía chown en ninguna parte. Reproducido en un contenedor antes de arreglarlo: .env root:root 600, app.db y Entrada/ de root, las tres operaciones del agente fallando — y `test -f` de la sonda del panel dando ok, o sea verde justo en el caso roto. SERVICE_USER se resuelve ahora al principio (antes se calculaba dentro del case de --service, después del bootstrap y de la siembra), conservando la misma cadena de precedencia. El chown va después de ambos pasos, solo cuando el script corre como root y el servicio no es root, y solo del usuario —no del grupo—; `chown` no toca los modos, así que el 0600 del .env sobrevive. Con dos cuidados que no son opcionales: - Guarda contra un PREFIX de sistema: un `chown -R` sobre / o /opt sería catastrófico. Se rechazan las rutas de sistema y las de un solo componente, avisando en vez de abortar una instalación ya hecha. - Si el chown falla se reporta como ERROR con el comando de arreglo, no se traga. Es el mismo criterio que el repo ya aplica al icacls de Windows: no se asume que un comando de endurecimiento tuvo éxito. Probado: arreglo, guarda de /opt, usuario inexistente, y sin sudo (no intenta nada). Windows no necesita el arreglo simétrico y queda documentado por qué: la tarea corre como SYSTEM con RunLevel Highest y install.ps1 no restringe ninguna ACL, así que hereda de su carpeta padre y puede leer todo lo del Administrador. --- install.ps1 | 9 +++++++++ install.sh | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/install.ps1 b/install.ps1 index ce0b9dd..f21ee0e 100644 --- a/install.ps1 +++ b/install.ps1 @@ -193,6 +193,15 @@ switch ($Mode) { Write-Step 'Registrando tarea programada ONSTART (SYSTEM)' # Sin NSSM: una tarea ONSTART como SYSTEM cubre el 24/7 headless con lo que ya # trae el SO. QT_QPA_PLATFORM=offscreen porque SYSTEM no tiene sesión gráfica. + # + # Correr como SYSTEM es además lo que evita aquí el problema de propiedad que en Linux + # sí hay que resolver: allá el instalador crea el árbol como root pero el unit corre + # como un usuario común, que no podría leer su config/.env ni escribir su base local + # (de ahí el `chown -R` de install.sh). SYSTEM tiene control total sobre el sistema de + # archivos local y este script no restringe ninguna ACL del prefijo, así que hereda de + # su carpeta padre y puede leer y escribir todo lo que creó el Administrador. No hace + # falta un arreglo simétrico; si algún día se cambia el principal a una cuenta común, + # entonces sí habría que ajustar los permisos del árbol. $action = New-ScheduledTaskAction -Execute $dest ` -Argument '--start-engine --headless' -WorkingDirectory $Prefix $trigger = New-ScheduledTaskTrigger -AtStartup diff --git a/install.sh b/install.sh index 8f29fa9..cc67b9b 100755 --- a/install.sh +++ b/install.sh @@ -66,6 +66,10 @@ UNIT_NAME="cloudrestoreas.service" # línea de cron del vigilante contiene esta ruta, así que el `sh -c` que la ejecuta hace # match consigo mismo, el vigilante cree que el agente corre y no lo rearranca nunca. PGREP_PAT="^$PREFIX/$BIN_NAME" +# Usuario del servicio. Se resuelve AQUÍ y no dentro del case de --service porque el chown de +# propiedad tiene que correr antes, justo después del bootstrap. Precedencia: la variable +# explícita, luego quien invocó el sudo, luego el usuario efectivo. +SERVICE_USER="${SERVICE_USER:-${SUDO_USER:-$(id -un)}}" # Localizar el binario: dist/CloudRestoreAS, dist/CloudRestoreAS-linux, o junto al script. SRC="" @@ -269,9 +273,51 @@ if [[ -n "$PANEL_ENV_FILE" ]]; then rm -f "$PANEL_ENV_FILE" fi +# --- Propiedad del árbol ------------------------------------------------------- +# Corriendo con sudo, el bootstrap crea config/, config/data/app.db, config/logs/, Entrada/, +# Procesados/, Fallados/ y Temp/ como ROOT, y la siembra escribe config/.env en 0600 de root. +# Pero el unit se registra con User=$SERVICE_USER, así que el agente arrancaría sin poder leer su +# propia configuración ni escribir su base local: falla en bucle, y la sonda del panel lo pinta +# verde porque comprueba existencia y no lectura. Devolver la propiedad es lo que cierra eso. +# +# Solo el usuario, no el grupo: basta para que el agente escriba y evita sorpresas con el grupo +# primario del destino. Y `chown` no toca los modos, así que el 0600 del .env sobrevive. +chown_tree_is_safe() { + local path="$1" + # Un chown -R sobre una ruta de sistema sería catastrófico. El panel ya valida la ruta, pero + # este script también se corre a mano. + case "$path" in + /|/opt|/usr|/etc|/var|/home|/srv|/root|/bin|/sbin|/lib|/lib64|/tmp) return 1 ;; + esac + # Al menos dos componentes: /algo/otro. + case "${path#/}" in + */*) return 0 ;; + *) return 1 ;; + esac +} + +if [[ "$MODE" != "update-in-place" && "$(id -u)" -eq 0 && "$SERVICE_USER" != "root" ]]; then + if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then + echo "AVISO: el usuario '$SERVICE_USER' no existe en este servidor; no se cambia la" >&2 + echo " propiedad de $PREFIX. Revisa SERVICE_USER." >&2 + elif ! chown_tree_is_safe "$PREFIX"; then + echo "AVISO: $PREFIX es una ruta de sistema; no se hace chown -R sobre ella." >&2 + echo " Ajústala a mano si el servicio corre como '$SERVICE_USER'." >&2 + elif chown -R "$SERVICE_USER" "$PREFIX"; then + echo "Propiedad de $PREFIX asignada a $SERVICE_USER." + else + # No se asume que un comando de endurecimiento tuvo éxito: si falla, el agente no arranca + # y es mejor decirlo ahora que dejar un servicio reiniciándose en silencio. + echo "ERROR: no se pudo asignar la propiedad de $PREFIX a $SERVICE_USER." >&2 + echo " El servicio corre como ese usuario y no podría leer config/.env ni escribir" >&2 + echo " su base local. Corrígelo antes de arrancarlo:" >&2 + echo " chown -R $SERVICE_USER $PREFIX" >&2 + exit 1 + fi +fi + case "$MODE" in service) - SERVICE_USER="${SERVICE_USER:-${SUDO_USER:-$(id -un)}}" UNIT_SRC="$SCRIPT_DIR/packaging/linux/$UNIT_NAME" UNIT_DST="/etc/systemd/system/$UNIT_NAME" if [[ ! -w "$(dirname "$UNIT_DST")" ]]; then