feat(install): --update-in-place, actualización sin privilegios y reversible
Hay entornos donde no se usa root en absoluto, así que ni `sudo -n` ni una cuenta root son opciones. Este modo actualiza una instalación existente dejando su unit de systemd intacto, que es lo único que una actualización necesita de verdad: reemplazar el binario y reiniciar el proceso. Se apoya en dos hechos, uno de ellos contrario a lo que decía el propio repo: - `install` NO sufre ETXTBSY. A diferencia de `cp` —que abre con O_TRUNC—, desvincula el destino antes de crearlo, y por eso `make install` funciona sobre binarios en ejecución. Comprobado: `cp` sobre un ELF corriendo da "Text file busy" y `install` no. La consecuencia es la que importa: reemplazar el binario exige escritura en el DIRECTORIO, no en el archivo. El comentario de install.sh, BUILD.md y el CHANGELOG afirmaban lo contrario y mandaban al operador a diagnosticar un archivo en uso cuando lo que tenía era un EACCES. - El unit corre como el usuario que instaló (cadena SUDO_USER) y trae Restart=always, así que esa cuenta puede señalizar el proceso y systemd lo relevanta con el binario nuevo. No hace falta systemctl ni tocar /etc. Robustez, que es donde estaba el trabajo real: - **Reversible.** Respalda el binario antes de reemplazarlo y, si el nuevo no arranca, lo restaura y confirma que el proceso volvió. Sin esto, una actualización fallida deja el servidor sin agente. Si tampoco puede revertir, conserva el respaldo y lo dice en vez de fingir éxito. - **No interrumpe restauraciones.** El agente no atiende SIGTERM: matarlo a media restauración deja ese respaldo vetado para siempre (has_blocking_job_by_hash) y puede dejar la base en SINGLE_USER. Se comprueba Temp/ dos veces —antes de copiar y otra vez justo antes de señalizar, para cerrar la ventana— y sale con 75 (EX_TEMPFAIL), que el panel traduce a "reintenta luego" y no a un fallo. - **Diagnostica por qué no volvió**: distingue un unit sin Restart=always de un StartLimitBurst agotado, con el comando de recuperación. - Omite el bootstrap de 20 s: es redundante en una actualización (ensure_runtime_layout corre en cada arranque) y una segunda instancia junto a la viva purgaría el Temp de la que está trabajando. Un bug que solo aparecía fuera del camino feliz: con `set -euo pipefail`, un `$(pgrep ... | head -1)` sin resultados hace fallar la sustitución y `set -e` mataba el script en silencio — justo en el caso "el proceso no volvió", que es el que había que manejar. Por eso el rollback no se ejecutaba nunca.
This commit is contained in:
@@ -31,8 +31,9 @@
|
||||
claves `CLOUDRESTORE_PANEL_*` en `config/.env` (replace-or-append, idempotente, con lista
|
||||
blanca) y borran el archivo. El token viaja por archivo 0600, nunca por argumentos, para
|
||||
que no quede visible en `ps` ni en el historial del destino.
|
||||
- `install.sh` detiene el servicio antes de reemplazar el binario (un ELF en ejecución da
|
||||
`ETXTBSY`) y lo vuelve a levantar si estaba activo.
|
||||
- `install.sh` detiene el servicio antes de reemplazar el binario y lo vuelve a levantar si
|
||||
estaba activo. (La razón que se dio aquí —que un ELF en ejecución da `ETXTBSY`— era incorrecta:
|
||||
eso le pasa a `cp`, no a `install`, que desvincula el destino antes de crearlo. Ver BUILD.md.)
|
||||
|
||||
#### Versionado
|
||||
- `app/__init__.py` es la fuente única de la versión; el diálogo *Acerca de* ya no la trae
|
||||
|
||||
Reference in New Issue
Block a user