Despliegue en Windows con NSSM en lugar de WinSW
El servicio de Windows pasa a administrarse con NSSM (decisión del usuario: WinSW no se consideró lo bastante estable). - instalar-servicio.ps1: recibe -Nssm (ruta de nssm.exe) y configura el servicio con `nssm install` / `nssm set` (arranque automático, reinicio a los 10 s, logs a logs\ rotados a los 10 MB). Nueva opción -SoloMostrar que imprime los comandos sin ejecutarlos ni exigir administrador. Si un ajuste falla, quita el servicio a medio configurar. - desinstalar-servicio.ps1: usa `nssm stop` y `nssm remove`. - Se elimina la plantilla XML de WinSW. - DEPLOY.md: pasos con NSSM, sección para administrar el servicio, aviso de usar la compilación 2.24-101 en Windows 10/11 y Server 2019/2022 (la 2.24 estable puede no arrancar), y limpieza de logs rotados (NSSM no limita cuántos conserva). Ya no se requiere .NET Framework. - CLAUDE.md y README de la API actualizados. Sin probar aún en Windows: solo se comprobó la sintaxis y los comandos generados (-SoloMostrar). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
287b187c14
commit
699dd983d0
6 files changed
+147
-99
No files matched your search
@@ -67,11 +67,12 @@ Pasos de la UI (decididos):
|
||||
- **Despliegue en Windows**: un solo servicio. La API sirve también la web compilada (mismo origen: sin
|
||||
CORS y con la cookie `SameSite=strict`), con **HTTPS directo en Node** (`HTTPS_PFX_PATH` o
|
||||
`HTTPS_KEY_PATH`+`HTTPS_CERT_PATH`) y redirección opcional de HTTP (`HTTP_REDIRECT_PORT`). El servicio
|
||||
lo administra **WinSW** (elegido sobre NSSM por su configuración en XML versionable); nginx no es
|
||||
necesario. `npm run publicar` arma `release/devolucion-proveedores/` (API + web + solo dependencias de
|
||||
lo administra **NSSM** (decisión del usuario; se evaluó WinSW y no se lo consideró lo bastante estable);
|
||||
nginx no es necesario. NSSM 2.24 estable puede no arrancar en Windows recientes: usar la 2.24-101. `npm run publicar` arma `release/devolucion-proveedores/` (API + web + solo dependencias de
|
||||
producción, ~55 MB). Guía completa: `deploy/windows/DEPLOY.md`.
|
||||
- **Logs**: la aplicación **no escribe logs a archivo**; usa el `Logger` de Nest por consola. En
|
||||
producción WinSW captura la salida y la rota (10 MB × 8). Con `NO_COLOR=1` el log sale sin códigos de
|
||||
producción NSSM captura la salida y la rota a los 10 MB (no limita cuántos archivos conserva: hay que
|
||||
limpiar los viejos). Con `NO_COLOR=1` el log sale sin códigos de
|
||||
color. Solo se registran errores 4xx/5xx, fallos o lentitud del Service Layer y eventos de negocio.
|
||||
- Aplicación web **independiente**. Login con el usuario de SAP B1 vía **Login del Service Layer**.
|
||||
- Metadatos (UDT/UDF/UDO) definidos en **JSON** con formato del Service Layer. La web tiene la
|
||||
@@ -109,7 +110,7 @@ devolucion-proveedores-web/ npm workspaces
|
||||
│ ├── web-estatico.ts sirve el build de Angular, fallback a index.html, cabeceras de seguridad
|
||||
│ └── https.ts lectura del certificado y redirección HTTP → HTTPS
|
||||
├── scripts/publicar.mjs arma la carpeta de despliegue (release/, ignorada por git)
|
||||
├── deploy/windows/ WinSW (plantilla XML), instalar/desinstalar-servicio.ps1, DEPLOY.md
|
||||
├── deploy/windows/ NSSM: instalar/desinstalar-servicio.ps1, DEPLOY.md
|
||||
└── apps/web/ Angular 22 standalone, signals, zoneless, CSS (no SCSS)
|
||||
└── src/app/
|
||||
├── core/ auth.service, metadata.service, planificaciones.service,
|
||||
@@ -262,8 +263,8 @@ Reglas:
|
||||
Para repetirlo hace falta una Devolución con folio (la que emite el addon de facturación electrónica).
|
||||
- **Despliegue en Windows**: la carpeta publicada se probó en un Mac (API con HTTPS autofirmado, web
|
||||
servida por la API, rutas de Angular, 404, cabeceras, redirección 301, log sin colores). **No se ha
|
||||
ejecutado en Windows**: los `.ps1` solo pasaron la comprobación de sintaxis y la generación del XML de
|
||||
WinSW; falta instalar el servicio de verdad. Las banderas de la cookie de sesión están comprobadas en un
|
||||
ejecutado en Windows**: los `.ps1` solo pasaron la comprobación de sintaxis y la lista de comandos de
|
||||
NSSM que generan (`-SoloMostrar`); falta instalar el servicio de verdad. Las banderas de la cookie de sesión están comprobadas en un
|
||||
test e2e; solo falta verlo en un navegador real sobre HTTPS (iniciar sesión en el sitio desplegado).
|
||||
- **Estilos:** son CSS porque, en el Mac con macOS 12, el compilador nativo de Sass exige
|
||||
macOS 14 o superior. En Sequoia se puede volver a SCSS si se prefiere.
|
||||
|
||||
+2
-2
@@ -83,9 +83,9 @@ en el mismo origen y en un solo puerto. Las rutas de Angular (sin extensión y f
|
||||
npm run publicar # desde la raíz: arma release/devolucion-proveedores/
|
||||
```
|
||||
|
||||
Instalación en Windows (servicio con WinSW, HTTPS, firewall, actualizaciones): [`../../deploy/windows/DEPLOY.md`](../../deploy/windows/DEPLOY.md).
|
||||
Instalación en Windows (servicio con NSSM, HTTPS, firewall, actualizaciones): [`../../deploy/windows/DEPLOY.md`](../../deploy/windows/DEPLOY.md).
|
||||
|
||||
**Logs:** la API no escribe archivos; usa el `Logger` de Nest por consola. Como servicio, WinSW captura
|
||||
**Logs:** la API no escribe archivos; usa el `Logger` de Nest por consola. Como servicio, NSSM captura
|
||||
esa salida y la rota. Con `NO_COLOR=1` el log sale sin códigos de color.
|
||||
|
||||
## Estructura
|
||||
|
||||
+49
-16
@@ -1,7 +1,7 @@
|
||||
# Despliegue en Windows
|
||||
|
||||
La aplicación se ejecuta como **un solo servicio de Windows**: la API (NestJS) sirve también la web
|
||||
compilada (Angular), por HTTPS y en un único puerto. El servicio lo administra **WinSW**.
|
||||
compilada (Angular), por HTTPS y en un único puerto. El servicio lo administra **NSSM**.
|
||||
|
||||
```
|
||||
Navegador ──HTTPS :443──▶ servicio "DevolucionProveedores" (node dist\main.js)
|
||||
@@ -15,21 +15,26 @@ memoria de la API, **reiniciar el servicio cierra la sesión de todos los usuari
|
||||
|
||||
> **Estado de esta guía.** La carpeta publicada, la API con HTTPS, la web servida por la API, la
|
||||
> redirección HTTP→HTTPS y los logs sin colores se probaron en un Mac. Los scripts de PowerShell y
|
||||
> WinSW **no se han ejecutado todavía en Windows** (se comprobó su sintaxis y la generación del XML).
|
||||
> Tampoco se ha probado contra HANA. Haga la primera instalación en un servidor de pruebas.
|
||||
> NSSM **no se han ejecutado todavía en Windows** (se comprobó su sintaxis y los comandos de NSSM que
|
||||
> generan, con `-SoloMostrar`). Tampoco se ha probado contra HANA. Haga la primera instalación en un servidor de pruebas.
|
||||
|
||||
## 1. Requisitos del servidor
|
||||
|
||||
| Qué | Detalle |
|
||||
|---|---|
|
||||
| Windows | Windows Server 2016 o superior. |
|
||||
| .NET Framework | 4.6.1 o superior (lo necesita WinSW; viene con Windows Server 2016+). |
|
||||
| Node.js | **22 LTS** o superior, instalado para todos los usuarios (`node -v` en una consola nueva). |
|
||||
| Red | Acceso al Service Layer de SAP (puerto 50000) y al web service de BES (HTTPS de salida). |
|
||||
| WinSW | `WinSW-x64.exe` de <https://github.com/winsw/winsw/releases> (rama 2.x). Verifique el hash que publica el proyecto. |
|
||||
| NSSM | `nssm.exe` de 64 bits (carpeta `win64` del `.zip` de <https://nssm.cc/download>). Ver la nota de abajo sobre la versión. |
|
||||
| Certificado | Un `.pfx` (o `.key` + `.crt`) para el nombre con que los usuarios abrirán la página. |
|
||||
| Permisos | Una consola de PowerShell **como administrador**. |
|
||||
|
||||
> **Versión de NSSM.** Descargue la compilación **2.24-101** (*pre-release*, `nssm-2.24-101-g897c7ad.zip`,
|
||||
> carpeta `win64`), no la estable. La estable (2.24, de 2014) puede fallar al iniciar servicios en Windows 10
|
||||
> y 11 y en Windows Server 2019 o 2022 (mensajes como `Unexpected status SERVICE_PAUSED`); solo debería
|
||||
> servir en Windows Server 2016. Compruebe con `nssm version` (debe decir `2.24-101-g897c7ad`). La página
|
||||
> de descargas no publica sumas de verificación: revise el `.zip` según la política de su empresa.
|
||||
|
||||
## 2. Publicar (en su equipo de desarrollo o en el servidor)
|
||||
|
||||
Desde la raíz del proyecto, con Node 22:
|
||||
@@ -55,9 +60,9 @@ C:\apps\devolucion-proveedores\
|
||||
│ └── apps\api\.env ← se crea en el paso 4
|
||||
├── certificados\ ← el certificado HTTPS (paso 5)
|
||||
├── logs\ ← los logs del servicio
|
||||
├── servicio\ ← lo crea el instalador (copia de nssm.exe)
|
||||
├── instalar-servicio.ps1
|
||||
├── desinstalar-servicio.ps1
|
||||
├── devolucion-api.xml.plantilla
|
||||
└── DEPLOY.md
|
||||
```
|
||||
|
||||
@@ -86,7 +91,7 @@ HTTP_REDIRECT_PORT=80
|
||||
|
||||
- **El puerto es el que ponga en `PORT`**: 443 es solo el ejemplo (con él la URL no lleva `:puerto`). Puede
|
||||
usar cualquiera que esté libre, por ejemplo `PORT=8443`; entonces la URL es `https://servidor:8443`.
|
||||
WinSW no influye: solo arranca el servicio.
|
||||
NSSM no influye: solo arranca el servicio.
|
||||
- `HTTP_REDIRECT_PORT` es opcional. Solo es útil con el 80 (la gente escribe `http://servidor` sin
|
||||
puerto); con otro puerto solo redirige a quien escriba `http://servidor:<ese puerto>`. Déjelo vacío si
|
||||
no lo necesita.
|
||||
@@ -128,7 +133,7 @@ New-NetFirewallRule -DisplayName 'Devolucion Proveedores HTTP (redireccion)' -Di
|
||||
|
||||
```powershell
|
||||
cd C:\apps\devolucion-proveedores
|
||||
.\instalar-servicio.ps1 -WinSW C:\descargas\WinSW-x64.exe
|
||||
.\instalar-servicio.ps1 -Nssm C:\descargas\nssm-2.24-101-g897c7ad\win64\nssm.exe
|
||||
```
|
||||
|
||||
Opciones:
|
||||
@@ -138,9 +143,11 @@ Opciones:
|
||||
| `-Cuenta 'EMPRESA\svc-devolucion'` | Ejecutar con una cuenta propia en vez de `LocalSystem`. Pide la contraseña y no la guarda en ningún archivo. Esa cuenta necesita el derecho *Iniciar sesión como servicio* y lectura sobre la carpeta. |
|
||||
| `-UsarCertificadosDelSistema` | Agrega `NODE_OPTIONS=--use-system-ca`. Úselo si Fortinet (u otro firewall) inspecciona el HTTPS de salida con un certificado propio y la descarga de PDF falla por certificado. |
|
||||
| `-NombreServicio` | Cambia el nombre del servicio (por defecto `DevolucionProveedores`). |
|
||||
| `-SoloMostrar` | Imprime los comandos de NSSM que ejecutaría y termina, sin instalar nada ni exigir administrador. Úselo para revisarlos antes. |
|
||||
|
||||
El script verifica Node 22, los archivos y el `.env`; genera `servicio\devolucion-api.xml` y
|
||||
`servicio\devolucion-api.exe`; instala el servicio (arranque automático y reinicio si falla) y lo inicia.
|
||||
El script verifica Node 22, los archivos y el `.env`; copia `nssm.exe` a `servicio\`; instala el servicio
|
||||
(arranque automático, reinicio a los 10 s si Node se cae, logs a `logs\`) y lo inicia. Si algún ajuste
|
||||
falla, quita el servicio a medio configurar.
|
||||
|
||||
## 8. Verificar
|
||||
|
||||
@@ -156,15 +163,40 @@ En una base nueva pulse **Reconstruir** para crear las tablas, campos y el objet
|
||||
|
||||
## 9. Logs
|
||||
|
||||
WinSW guarda lo que la API escribe por consola en `logs\` (`*.out.log` y `*.err.log`), rotado por
|
||||
tamaño: 10 MB y 8 archivos. La aplicación no escribe logs por su cuenta.
|
||||
NSSM guarda lo que la API escribe por consola en `logs\devolucion-api.out.log` y `devolucion-api.err.log`,
|
||||
y los rota al llegar a 10 MB (los rotados llevan la fecha y la hora en el nombre). La aplicación no escribe
|
||||
logs por su cuenta.
|
||||
|
||||
**NSSM no limita cuántos archivos rotados conserva**: se acumulan. Programe una limpieza periódica, por
|
||||
ejemplo una tarea que borre los de más de 30 días:
|
||||
|
||||
```powershell
|
||||
Get-ChildItem C:\apps\devolucion-proveedores\logs -Filter 'devolucion-api.*-*.log' |
|
||||
Where-Object LastWriteTime -lt (Get-Date).AddDays(-30) | Remove-Item
|
||||
```
|
||||
|
||||
- Los errores 4xx/5xx de la API, los fallos y las peticiones lentas al Service Layer, y los eventos de
|
||||
negocio (planificación creada, PDF descargado) quedan registrados. Las peticiones correctas no.
|
||||
- Para seguirlo en vivo: `Get-Content C:\apps\devolucion-proveedores\logs\*.out.log -Wait -Tail 50`.
|
||||
- La contraseña del web service DTE (`@DTE_CONFC`) y las de los usuarios **nunca** se registran.
|
||||
|
||||
## 10. Actualizar a una versión nueva
|
||||
## 10. Administrar el servicio
|
||||
|
||||
Con `servicio\nssm.exe` (la copia que dejó el instalador):
|
||||
|
||||
```powershell
|
||||
cd C:\apps\devolucion-proveedores\servicio
|
||||
.\nssm.exe status DevolucionProveedores
|
||||
.\nssm.exe restart DevolucionProveedores
|
||||
.\nssm.exe edit DevolucionProveedores # ventana gráfica con todos los ajustes
|
||||
.\nssm.exe get DevolucionProveedores AppEnvironmentExtra
|
||||
```
|
||||
|
||||
Para cambiar una variable de entorno del servicio con `nssm set ... AppEnvironmentExtra`, indique **todas** las
|
||||
variables juntas (reemplaza la lista completa): `NODE_ENV=production NO_COLOR=1 [NODE_OPTIONS=--use-system-ca]`.
|
||||
Los valores de la aplicación (puerto, SAP, HTTPS) no van aquí: se cambian en el `.env` y se reinicia el servicio.
|
||||
|
||||
## 11. Actualizar a una versión nueva
|
||||
|
||||
1. Publique la versión nueva (paso 2) y copie la carpeta al servidor, por ejemplo a `C:\nueva\`.
|
||||
2. Elija una hora en que haya pocos usuarios: **se perderán las sesiones**.
|
||||
@@ -182,7 +214,7 @@ Start-Service DevolucionProveedores
|
||||
`/MIR` borra de `app\` lo que ya no exista en la versión nueva; `/XF .env` conserva su configuración.
|
||||
Los certificados, los logs y el servicio no cambian.
|
||||
|
||||
## 11. Desinstalar
|
||||
## 12. Desinstalar
|
||||
|
||||
```powershell
|
||||
cd C:\apps\devolucion-proveedores
|
||||
@@ -191,7 +223,7 @@ cd C:\apps\devolucion-proveedores
|
||||
|
||||
Detiene y elimina el servicio. No borra archivos ni logs.
|
||||
|
||||
## 12. Seguridad
|
||||
## 13. Seguridad
|
||||
|
||||
- **No publique la aplicación en Internet.** El login es el de SAP B1; manténgala en la red interna o
|
||||
detrás de una VPN.
|
||||
@@ -199,10 +231,11 @@ Detiene y elimina el servicio. No borra archivos ni logs.
|
||||
- Dele al servicio la cuenta con menos permisos posible: solo lectura sobre su carpeta y escritura en `logs\`.
|
||||
- Restrinja la lectura del `.env` si contiene la clave del `.pfx` (paso 4).
|
||||
|
||||
## 13. Problemas frecuentes
|
||||
## 14. Problemas frecuentes
|
||||
|
||||
| Síntoma | Causa probable |
|
||||
|---|---|
|
||||
| El servicio no inicia (`SERVICE_PAUSED`, `Unexpected status`) | Problema conocido de NSSM 2.24 estable en Windows recientes: use la compilación 2.24-101 (ver el paso 1) y reinstale el servicio. |
|
||||
| El servicio se inicia y se detiene | Mire `logs\*.err.log`. Suele ser una ruta mal escrita en el `.env` (`No se pudo leer HTTPS_PFX_PATH (…)`) o el puerto ocupado (`EADDRINUSE`). |
|
||||
| `WEB_DIST_DIR no contiene un index.html` | Se copió la carpeta `app` incompleta: falta `app\apps\web\dist\web\browser`. |
|
||||
| Puerto 443 o 80 ocupado | IIS u otro programa lo usa. Cambie `PORT` / `HTTP_REDIRECT_PORT` en el `.env` y las reglas del firewall. |
|
||||
|
||||
@@ -2,24 +2,28 @@
|
||||
.SYNOPSIS
|
||||
Detiene y elimina el servicio de Windows. No borra los archivos ni los logs.
|
||||
#>
|
||||
#Requires -RunAsAdministrator
|
||||
[CmdletBinding()]
|
||||
param([string]$NombreServicio = 'DevolucionProveedores')
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$exe = Join-Path $PSScriptRoot 'servicio\devolucion-api.exe'
|
||||
|
||||
$esAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
if (-not $esAdmin) { Write-Host 'ERROR: Ejecute este script en una consola de PowerShell como administrador.' -ForegroundColor Red; exit 1 }
|
||||
|
||||
if (-not (Get-Service -Name $NombreServicio -ErrorAction SilentlyContinue)) {
|
||||
Write-Host "El servicio '$NombreServicio' no existe."
|
||||
exit 0
|
||||
}
|
||||
if (-not (Test-Path $exe)) {
|
||||
Write-Host "No se encontró $exe; se intentará con sc.exe."
|
||||
& sc.exe stop $NombreServicio | Out-Null
|
||||
& sc.exe delete $NombreServicio
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
& $exe stop
|
||||
& $exe uninstall
|
||||
$nssm = Join-Path $PSScriptRoot 'servicio\nssm.exe'
|
||||
if (Test-Path $nssm) {
|
||||
& $nssm stop $NombreServicio | Out-Null # si ya estaba detenido, no es un error
|
||||
& $nssm remove $NombreServicio confirm | Out-Null
|
||||
} else {
|
||||
Write-Host "No se encontró $nssm; se usa sc.exe."
|
||||
& sc.exe stop $NombreServicio | Out-Null
|
||||
& sc.exe delete $NombreServicio | Out-Null
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host "ERROR: no se pudo eliminar el servicio '$NombreServicio'." -ForegroundColor Red; exit 1 }
|
||||
|
||||
Write-Host "Servicio '$NombreServicio' eliminado. Los archivos y los logs siguen en $PSScriptRoot."
|
||||
@@ -1,32 +0,0 @@
|
||||
<!--
|
||||
Configuración de WinSW. instalar-servicio.ps1 reemplaza los marcadores (nombre entre dos guiones bajos a cada lado) y guarda el
|
||||
resultado como servicio\devolucion-api.xml (debe llamarse igual que el .exe de WinSW).
|
||||
-->
|
||||
<service>
|
||||
<id>__ID__</id>
|
||||
<name>Devolución a Proveedores</name>
|
||||
<description>API y web de Devolución a Proveedores (SAP Business One)</description>
|
||||
|
||||
<executable>__NODE__</executable>
|
||||
<arguments>dist\main.js</arguments>
|
||||
<!-- Debe ser apps\api: de ahí se lee el archivo .env -->
|
||||
<workingdirectory>__RAIZ__\app\apps\api</workingdirectory>
|
||||
|
||||
<env name="NODE_ENV" value="production"/>
|
||||
<!-- Sin colores: en un archivo de log los códigos de color salen como basura -->
|
||||
<env name="NO_COLOR" value="1"/>
|
||||
__NODE_OPTIONS__
|
||||
<startmode>Automatic</startmode>
|
||||
<onfailure action="restart" delay="10 sec"/>
|
||||
<onfailure action="restart" delay="30 sec"/>
|
||||
<onfailure action="none"/>
|
||||
<resetfailure>1 hour</resetfailure>
|
||||
<stoptimeout>15 sec</stoptimeout>
|
||||
|
||||
<!-- Lo que la API escribe por consola (logs de Nest) queda aquí, rotado por tamaño -->
|
||||
<logpath>__RAIZ__\logs</logpath>
|
||||
<log mode="roll-by-size">
|
||||
<sizeThreshold>10240</sizeThreshold>
|
||||
<keepFiles>8</keepFiles>
|
||||
</log>
|
||||
</service>
|
||||
@@ -1,76 +1,118 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Instala la API (que también sirve la web) como servicio de Windows usando WinSW.
|
||||
Instala la API (que también sirve la web) como servicio de Windows usando NSSM.
|
||||
|
||||
.EXAMPLE
|
||||
.\instalar-servicio.ps1 -WinSW C:\descargas\WinSW-x64.exe
|
||||
.\instalar-servicio.ps1 -Nssm C:\descargas\nssm\win64\nssm.exe
|
||||
|
||||
.EXAMPLE
|
||||
.\instalar-servicio.ps1 -WinSW C:\descargas\WinSW-x64.exe -Cuenta 'EMPRESA\svc-devolucion' -UsarCertificadosDelSistema
|
||||
.\instalar-servicio.ps1 -Nssm C:\descargas\nssm\win64\nssm.exe -Cuenta 'EMPRESA\svc-devolucion' -UsarCertificadosDelSistema
|
||||
|
||||
.EXAMPLE
|
||||
# Muestra los comandos que ejecutaría, sin instalar nada ni exigir administrador
|
||||
.\instalar-servicio.ps1 -Nssm C:\descargas\nssm\win64\nssm.exe -SoloMostrar
|
||||
#>
|
||||
#Requires -RunAsAdministrator
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# Ruta del ejecutable de WinSW (WinSW-x64.exe, de https://github.com/winsw/winsw/releases)
|
||||
[Parameter(Mandatory)][string]$WinSW,
|
||||
# Ruta de nssm.exe (la versión de 64 bits, carpeta win64 del .zip de https://nssm.cc/download)
|
||||
[Parameter(Mandatory)][string]$Nssm,
|
||||
# Cuenta con la que corre el servicio (DOMINIO\usuario). Si se omite: LocalSystem.
|
||||
[string]$Cuenta,
|
||||
[string]$NombreServicio = 'DevolucionProveedores',
|
||||
# Agrega NODE_OPTIONS=--use-system-ca: necesario si Fortinet u otro firewall inspecciona el tráfico
|
||||
# HTTPS de salida (por ejemplo hacia el web service de BES) con un certificado propio.
|
||||
[switch]$UsarCertificadosDelSistema
|
||||
[switch]$UsarCertificadosDelSistema,
|
||||
# Solo imprime los comandos de NSSM que se ejecutarían.
|
||||
[switch]$SoloMostrar
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$raiz = $PSScriptRoot
|
||||
$apiDir = Join-Path $raiz 'app\apps\api'
|
||||
$logsDir = Join-Path $raiz 'logs'
|
||||
|
||||
function Fallar($mensaje) { Write-Host "ERROR: $mensaje" -ForegroundColor Red; exit 1 }
|
||||
|
||||
if (-not $SoloMostrar) {
|
||||
$esAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
if (-not $esAdmin) { Fallar 'Ejecute este script en una consola de PowerShell como administrador.' }
|
||||
}
|
||||
|
||||
# --- Verificaciones previas -------------------------------------------------------------------
|
||||
if (-not (Test-Path $WinSW)) { Fallar "No se encontró WinSW en '$WinSW'." }
|
||||
if (-not (Test-Path $Nssm)) { Fallar "No se encontró nssm.exe en '$Nssm'." }
|
||||
|
||||
$node = Get-Command node -ErrorAction SilentlyContinue
|
||||
if (-not $node) { Fallar 'Node.js no está instalado (o no está en el PATH). Instale Node 22 LTS.' }
|
||||
$versionNode = [version](& $node.Source --version).TrimStart('v')
|
||||
if ($versionNode.Major -lt 22) { Fallar "Se necesita Node 22 o superior; hay $versionNode." }
|
||||
|
||||
if (-not (Test-Path (Join-Path $apiDir 'dist\main.js'))) { Fallar "Falta $apiDir\dist\main.js (¿se copió la carpeta app completa?)." }
|
||||
if (-not (Test-Path (Join-Path $apiDir 'node_modules')) -and -not (Test-Path (Join-Path $raiz 'app\node_modules'))) { Fallar 'Faltan las dependencias (app\node_modules).' }
|
||||
if (-not (Test-Path (Join-Path $apiDir 'dist\main.js'))) { Fallar "Falta $apiDir\dist\main.js (¿se copió la carpeta app completa?)." }
|
||||
if (-not (Test-Path (Join-Path $raiz 'app\node_modules'))) { Fallar 'Faltan las dependencias (app\node_modules).' }
|
||||
if (-not (Test-Path (Join-Path $raiz 'app\apps\web\dist\web\browser\index.html'))) { Fallar 'Falta la web compilada (app\apps\web\dist\web\browser).' }
|
||||
if (-not (Test-Path (Join-Path $apiDir '.env'))) { Fallar "Falta $apiDir\.env. Copie .env.example a .env y complételo (ver DEPLOY.md)." }
|
||||
|
||||
if (Get-Service -Name $NombreServicio -ErrorAction SilentlyContinue) {
|
||||
if (-not $SoloMostrar -and (Get-Service -Name $NombreServicio -ErrorAction SilentlyContinue)) {
|
||||
Fallar "El servicio '$NombreServicio' ya existe. Ejecute primero .\desinstalar-servicio.ps1."
|
||||
}
|
||||
|
||||
# --- Archivos del servicio ----------------------------------------------------------------------
|
||||
$servicioDir = Join-Path $raiz 'servicio'
|
||||
New-Item -ItemType Directory -Force -Path $servicioDir, (Join-Path $raiz 'logs') | Out-Null
|
||||
# --- Configuración del servicio (todo lo que se le pide a NSSM, en un solo lugar) -----------------
|
||||
$variablesDeEntorno = @('NODE_ENV=production', 'NO_COLOR=1') # NO_COLOR: sin códigos de color en los logs
|
||||
if ($UsarCertificadosDelSistema) { $variablesDeEntorno += 'NODE_OPTIONS=--use-system-ca' }
|
||||
|
||||
$exe = Join-Path $servicioDir 'devolucion-api.exe'
|
||||
$xml = Join-Path $servicioDir 'devolucion-api.xml'
|
||||
Copy-Item -Path $WinSW -Destination $exe -Force
|
||||
$ajustes = [System.Collections.Generic.List[string[]]]::new()
|
||||
function Agregar([string[]]$ajuste) { $ajustes.Add($ajuste) }
|
||||
|
||||
$nodeOptions = if ($UsarCertificadosDelSistema) { ' <env name="NODE_OPTIONS" value="--use-system-ca"/>' } else { '' }
|
||||
# .Replace() sustituye texto literal (las rutas pueden tener espacios, puntos o signos que -replace trataría como patrón)
|
||||
$contenido = (Get-Content (Join-Path $raiz 'devolucion-api.xml.plantilla') -Raw -Encoding UTF8).
|
||||
Replace('__ID__', $NombreServicio).
|
||||
Replace('__NODE__', $node.Source).
|
||||
Replace('__RAIZ__', $raiz).
|
||||
Replace('__NODE_OPTIONS__', $nodeOptions)
|
||||
if ($contenido -match '__[A-Z_]+__') { Fallar "La plantilla tiene un marcador sin reemplazar: $($Matches[0])" }
|
||||
# UTF-8 sin BOM
|
||||
[System.IO.File]::WriteAllText($xml, $contenido, (New-Object System.Text.UTF8Encoding($false)))
|
||||
Agregar 'DisplayName', 'Devolucion a Proveedores'
|
||||
Agregar 'Description', 'API y web de Devolucion a Proveedores (SAP Business One)'
|
||||
Agregar 'AppDirectory', $apiDir # de ahí se lee el archivo .env
|
||||
Agregar (@('AppEnvironmentExtra') + $variablesDeEntorno)
|
||||
Agregar 'Start', 'SERVICE_AUTO_START'
|
||||
Agregar 'AppStdout', (Join-Path $logsDir 'devolucion-api.out.log') # lo que la API escribe por consola
|
||||
Agregar 'AppStderr', (Join-Path $logsDir 'devolucion-api.err.log')
|
||||
Agregar 'AppRotateFiles', '1' # rota los logs al llegar a 10 MB,
|
||||
Agregar 'AppRotateOnline', '1' # incluso con el servicio en marcha
|
||||
Agregar 'AppRotateBytes', '10485760'
|
||||
Agregar 'AppExit', 'Default', 'Restart' # si Node se cae, lo reinicia
|
||||
Agregar 'AppRestartDelay', '10000' # esperando 10 s
|
||||
|
||||
$instalar = @('install', $NombreServicio, $node.Source, 'dist\main.js')
|
||||
|
||||
function Texto([string[]]$partes) {
|
||||
($partes | ForEach-Object { if ($_ -match '\s') { "`"$_`"" } else { $_ } }) -join ' '
|
||||
}
|
||||
|
||||
if ($SoloMostrar) {
|
||||
Write-Host "Comandos que se ejecutarían (nssm = $Nssm):`n"
|
||||
Write-Host "nssm $(Texto $instalar)"
|
||||
foreach ($a in $ajustes) { Write-Host "nssm $(Texto (@('set', $NombreServicio) + $a))" }
|
||||
Write-Host "nssm start $NombreServicio"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# --- Instalación --------------------------------------------------------------------------------
|
||||
New-Item -ItemType Directory -Force -Path (Join-Path $raiz 'servicio'), $logsDir | Out-Null
|
||||
|
||||
# El servicio queda apuntando a este nssm.exe, así que se guarda una copia en una carpeta estable.
|
||||
$nssmExe = Join-Path $raiz 'servicio\nssm.exe'
|
||||
Copy-Item -Path $Nssm -Destination $nssmExe -Force
|
||||
|
||||
function Ejecutar-Nssm([string[]]$argumentos) {
|
||||
& $nssmExe @argumentos | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { Fallar "nssm $(Texto $argumentos) falló (código $LASTEXITCODE)." }
|
||||
}
|
||||
|
||||
Write-Host "Instalando el servicio '$NombreServicio'..."
|
||||
& $exe install
|
||||
if ($LASTEXITCODE -ne 0) { Fallar 'WinSW no pudo instalar el servicio.' }
|
||||
Ejecutar-Nssm $instalar
|
||||
try {
|
||||
foreach ($a in $ajustes) { Ejecutar-Nssm (@('set', $NombreServicio) + $a) }
|
||||
} catch {
|
||||
& $nssmExe remove $NombreServicio confirm | Out-Null # no dejar un servicio a medio configurar
|
||||
throw
|
||||
}
|
||||
|
||||
if ($Cuenta) {
|
||||
# La contraseña se pide aquí y se pasa a Windows; no queda escrita en ningún archivo.
|
||||
$credencial = Get-Credential -UserName $Cuenta -Message "Contraseña de la cuenta del servicio"
|
||||
$credencial = Get-Credential -UserName $Cuenta -Message 'Contraseña de la cuenta del servicio'
|
||||
$clave = $credencial.GetNetworkCredential().Password
|
||||
& sc.exe config $NombreServicio obj= $credencial.UserName password= $clave | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { Fallar "No se pudo asignar la cuenta '$Cuenta' al servicio." }
|
||||
@@ -78,9 +120,9 @@ if ($Cuenta) {
|
||||
}
|
||||
|
||||
Write-Host 'Iniciando...'
|
||||
& $exe start
|
||||
Ejecutar-Nssm @('start', $NombreServicio)
|
||||
Start-Sleep -Seconds 3
|
||||
& $exe status
|
||||
& $nssmExe status $NombreServicio
|
||||
|
||||
Write-Host "`nLogs: $(Join-Path $raiz 'logs')" -ForegroundColor Green
|
||||
Write-Host "`nLogs: $logsDir" -ForegroundColor Green
|
||||
Write-Host 'Compruebe: Invoke-WebRequest <su-url>/api/health' -ForegroundColor Green
|
||||
Reference in new issue
Block a user