docs: documentar el instalador de Windows y corregir lo que contradecía al código

README afirmaba que la app no puede correr como servicio de Windows y sugería
NSSM, contradiciendo a install.ps1 desde que existe. LEEME.txt solo documentaba
el camino manual para Windows, mientras que para Linux ya traía el instalador.

BUILD.md gana -UpdateInPlace, las tres garantías al reemplazar el binario y el
remedio del token filtrado por UAC.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 08:46:28 -06:00
parent c2afa52d6f
commit 0a62b7d0aa
4 changed files with 93 additions and 4 deletions

View File

@@ -196,6 +196,7 @@ cd CloudRestoreAS
.\install.ps1 -Service # tarea programada ONSTART como SYSTEM (24/7 headless)
.\install.ps1 -Desktop # arranque al iniciar sesión (tarea ONLOGON de la app)
.\install.ps1 -UpdateInPlace # actualiza una instalación existente, conservando su tarea
.\install.ps1 # solo instala + bootstrap
Get-Help .\install.ps1 -Detailed
```
@@ -205,6 +206,30 @@ Parámetros: `-Prefix` (default `C:\Aduanasoft\CloudRestoreAS`), `-PanelEnvFile`
arranque 24/7 se resuelve con una tarea programada, no con NSSM: descargarlo violaría la
regla de que en el servidor destino no se instala ni se baja nada.
Si la cuenta pertenece a Administradores y aun así se rechaza, el mensaje lo dice explícitamente:
es el **token filtrado por UAC**, que es lo que recibe una sesión de OpenSSH. No se arregla
cambiando de cuenta sino con `LocalAccountTokenFilterPolicy=1` (DWORD) en
`HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System`.
### Garantías al reemplazar el binario (las mismas en los dos instaladores)
Reemplazar el binario de un servidor en producción no puede dejarlo sin restaurador:
1. **No se actúa si hay una restauración en curso** (`Temp\` no vacío): se sale con **75**
(`EX_TEMPFAIL`), que el PANEL traduce a "reintenta luego" y no a "falló la instalación".
Interrumpirla dejaría ese respaldo vetado en cada escaneo posterior y la base en `SINGLE_USER`.
2. **Se respalda el binario anterior** antes de pisarlo (`.CloudRestoreAS.exe.prev`).
3. **Se confirma que el agente volvió a arrancar** y, si no, se **revierte** al binario anterior.
El respaldo solo se descarta tras esa confirmación.
`-UpdateInPlace` además no vuelve a registrar la tarea (así no pisa ajustes hechos sobre ella) y
se salta el bootstrap: una segunda instancia purgaría el `Temp\` de la que está viva. Es el modo
que usa el PANEL para actualizar.
El arranque headless no depende del entorno de la tarea: `--headless` hace que el binario elija
el plugin Qt `offscreen` en cualquier plataforma, que es lo que le permite correr como SYSTEM en
la sesión 0, donde no hay escritorio interactivo.
> `scripts/dev-setup.ps1` es otra cosa: prepara el entorno de **desarrollo** (Python, venv,
> `requirements.txt`) para correr `python runner.py`. No sirve para desplegar el binario.

View File

@@ -1,5 +1,46 @@
# Changelog
## [Sin publicar]
### Instalación y actualización desatendidas en Windows
El instalador de Windows nunca recibió la maquinaria de actualización segura que sí tiene
`install.sh`, y la asimetría se notaba en producción: actualizar desde el PANEL dejaba el
servidor sin agente, o fallaba con un error que no correspondía.
- **`install.ps1 -UpdateInPlace`**: actualiza una instalación existente conservando su tarea
programada y su configuración, y sin correr el bootstrap (una segunda instancia purgaría el
`Temp\` de la que está viva). Es el modo que usa el PANEL para actualizar.
- **No se interrumpe una restauración en curso** (`Temp\` no vacío): sale con **75**
(`EX_TEMPFAIL`), que el PANEL traduce a "reintenta luego". Windows no tenía esta guarda y una
reinstalación a destiempo se llevaba por delante el respaldo que se estuviera restaurando,
dejándolo vetado y la base en `SINGLE_USER`.
- **Respaldo y reversión automática**: si el binario nuevo no arranca, se vuelve al anterior.
El respaldo solo se descarta tras confirmar que la versión nueva corre.
- **Rearranque garantizado en todos los modos.** La detención corría siempre, pero solo
`-Service` volvía a arrancar algo: actualizar con `desktop` o `none` mataba el agente y se iba
sin dejar señal.
- La espera a que el SO libere el `.exe` pasa de 5 s a 30 s con reintentos de la copia: con un
antivirus escaneando un onefile de ~270 MB, 5 s se quedaban cortos y la copia abortaba.
- Se distingue **"no es administrador"** de **"es administrador con el token filtrado por UAC"**,
que es lo que recibe una sesión de OpenSSH. Se veían idénticos y el remedio es el opuesto: el
segundo no se arregla cambiando de cuenta sino con `LocalAccountTokenFilterPolicy`.
### `--headless` ahora funciona en Windows
`_ensure_qt_platform()` salía de inmediato en `win32`, así que la bandera no hacía nada ahí. La
tarea ONSTART corre como SYSTEM en la sesión 0, sin escritorio interactivo, y arrancaba con el
plugin Qt `windows` intentando crear una ventana real. En Linux el mismo modo funcionaba porque
el unit de systemd fija `QT_QPA_PLATFORM=offscreen` por fuera, y esa asimetría escondió el
defecto. Ahora `--headless` fuerza `offscreen` en todas las plataformas y no se muestra ventana.
### Documentación
- `README.md` afirmaba que la app no puede correr como servicio de Windows y sugería NSSM, lo
que contradecía a `install.ps1` desde que existe. Corregido.
- `packaging/LEEME.txt` solo documentaba el camino manual para Windows; ahora incluye el
instalador, igual que ya hacía para Linux.
## [1.1.0] - 2026-07-29
### Distribución e instalación automatizada vía Gitea + PANEL

View File

@@ -472,7 +472,7 @@ R: Sí, especifica el nombre o IP del servidor remoto en Configuración → SQL
R: Sí, la aplicación usa RESTORE FILELISTONLY para detectar todos los archivos lógicos (MDF, LDF, y archivos adicionales). Solo mantiene el archivo principal de datos (.mdf) y de log (.ldf).
**P: ¿Puedo ejecutar esto como Windows Service?**
R: No, la aplicación está diseñada como "daemon de usuario" con interfaz gráfica. Para ejecutar como servicio, considera usar NSSM (Non-Sucking Service Manager) para envolver el ejecutable, pero perderás la UI.
R: Sí, con `install.ps1 -Service`: registra una tarea programada ONSTART que corre como SYSTEM, sin sesión iniciada y sin UI. **No** se usa NSSM ni ningún envoltorio descargado — el servidor destino no instala ni baja nada, y una tarea programada ya viene en el SO. Requiere PowerShell como Administrador. Ver [BUILD.md](BUILD.md) §6.
## 📄 Licencia

View File

@@ -18,6 +18,29 @@ Cerrar la ventana (X) minimiza a la bandeja; la app sigue en ejecucion.
Para salir por completo: clic derecho en el icono de bandeja -> Salir,
o menu Archivo -> Salir.
-------------------------------------
Windows en SERVIDOR (24/7, sin sesion iniciada)
-------------------------------------
Lo normal es que el PANEL instale y actualice solo, desde Versiones CRAS. Esto es
el camino manual, y es lo mismo que el PANEL ejecuta por dentro.
Opcion A - servicio 24/7 (recomendado en servidor SQL):
.\install.ps1 -Service
(registra una tarea programada ONSTART que corre como SYSTEM, sin sesion
iniciada y sin ventana; ver estado: Get-ScheduledTask -TaskName CloudRestoreAS)
Requiere PowerShell como Administrador.
Opcion B - actualizar una instalacion que ya existe:
.\install.ps1 -UpdateInPlace
(reemplaza el binario conservando la tarea y la configuracion; no actua si hay
una restauracion en curso, y revierte solo si la version nueva no arranca)
Opcion C - escritorio Windows (con sesion):
.\install.ps1 -Desktop
No se usa NSSM ni ningun envoltorio de servicio: la tarea programada ya viene en el
SO, y en el servidor destino no se instala ni se descarga nada.
-------------------------------------
Linux en SERVIDOR SIN ESCRITORIO (headless)
-------------------------------------