T ToutadminDocumentación

Instalar la edición Node

La edición Node funciona como un servicio permanente: un proceso, una base SQLite en el disco, nada más que instalar —ni base de datos externa, ni caché, ni cola—. Este tutorial va del directorio vacío a la instancia en producción, incluido lo que la mayoría de las guías deja de lado: el servicio del sistema, el proxy inverso, el TLS, las copias de seguridad y la actualización.

Lo que hace falta#

ElementoVersiónPor qué
Node.js22 o más recienteEl producto se apoya en su lanzador de pruebas integrado y en API recientes.
Un compilador Cbuild-essentialbetter-sqlite3 se compila al instalarse, salvo que exista un binario precompilado para su plataforma.
Un disco localSQLite y los archivos depositados. Nunca un recurso compartido de red: véase más abajo.

Una máquina de un núcleo y 512 MB de memoria basta para unas decenas de personas. Lo que cuenta no es la potencia sino el disco: debe ser local y estar respaldado.

1. Obtener e instalar#

git clone <su-repositorio> /var/www/toutadmin
cd /var/www/toutadmin
npm ci --omit=dev

npm ci en lugar de npm install: instala exactamente lo que describe el archivo de bloqueo, sin reescribirlo nunca. En un servidor, una versión que se desplaza es una avería que nadie entiende.

Si la compilación falla

better-sqlite3 es la única dependencia que compila C. sudo apt install -y build-essential python3 resuelve la práctica totalidad de los casos.

2. Configurar#

cp .env.example .env

Todo se ajusta con variables de entorno, leídas desde .env o puestas por su gestor de servicios. Ninguna es obligatoria: sin .env, la instancia arranca en el puerto 3000 y le envía al asistente.

VariablePor defectoQué hace
PORT3000El puerto de escucha.
NODE_ENVproduction en producción: cookies estrictas, sin trazas detalladas.
SESSION_SECRETgeneradoSella las sesiones. Si se deja vacío, se genera en data/session.key.
INSTALL_TOKENLo reclama el asistente antes de instalar. Recomendado en un servidor expuesto.
TRUST_PROXY1 detrás de un proxy de confianza, y solo ahí.
DB_PATHdata/app.sqliteLa base.
UPLOAD_DIR, CV_DIR, VAULT_DIR, SIGN_DIR, DOCS_DIR, BACKUP_DIRbajo data/Las carpetas de archivos: fotos, CV, caja fuerte, portafirmas, documentos recibidos, archivos.
LOGIN_RATE_LIMIT10Intentos de acceso por cuarto de hora y por dirección.
GLOBAL_RATE_LIMIT300Peticiones por minuto y por dirección.
API_RATE_LIMITLlamadas de API por minuto y por token.
SESSION_IDLE_MINUTES60Inactividad tras la cual cae la sesión.
SESSION_MAX_HOURS12Duración absoluta que ninguna actividad prolonga.
ADMIN_EMAIL, ADMIN_PASSWORDInstalación sin interfaz: crea el administrador al arrancar.
El secreto de sesión

Cambiar SESSION_SECRET desconecta a todo el mundo de golpe. Póngalo una vez, genérelo al azar (node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"), y guárdelo con el resto. No lo deje nunca en un repositorio git.

Poner un token de instalación#

Entre el primer arranque y su paso por el asistente, la instancia es de quien la encuentre: el primero en llegar crea la cuenta de administración. Ponga, pues, INSTALL_TOKEN antes de abrir el puerto:

node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
# luego en .env:
INSTALL_TOKEN=c3f1…

El asistente lo compara en tiempo constante —una comparación ordinaria dejaría adivinar el token carácter a carácter— y registra cada rechazo. Una vez instalada la instancia, el asistente se cierra solo.

Instalar sin interfaz#

Para un despliegue automatizado, ADMIN_EMAIL y ADMIN_PASSWORD crean el administrador al arrancar, sin pasar por el asistente. Retírelos después: una contraseña en el entorno de un servicio la lee quien lea ese servicio.

3. Permisos y dónde viven los datos#

sudo useradd --system --home /var/www/toutadmin --shell /usr/sbin/nologin toutadmin
sudo chown -R toutadmin:toutadmin /var/www/toutadmin/data
sudo chmod 750 /var/www/toutadmin/data
sudo chmod 640 /var/www/toutadmin/.env

El código puede quedar en solo lectura; solo data/ debe ser escribible. SQLite escribe archivos vecinos (-wal, -shm): lo que cuenta es la carpeta, no solo la base.

Nunca en un recurso compartido de red

NFS y SMB mienten sobre el bloqueo de archivos. SQLite se apoya en ese bloqueo para impedir dos escrituras simultáneas: en un recurso compartido, la base acaba corrompiéndose sin avisar. Disco local, siempre —y es la copia de seguridad la que se va a otra parte, no la base—.

4. El servicio#

Lanzado a mano, el producto se detiene al cerrar la terminal. Confíelo a systemd:

# /etc/systemd/system/toutadmin.service
[Unit]
Description=Toutadmin
After=network.target

[Service]
Type=simple
User=toutadmin
WorkingDirectory=/var/www/toutadmin
EnvironmentFile=/var/www/toutadmin/.env
ExecStart=/usr/bin/node src/server.js
Restart=always
RestartSec=5

# El servicio solo necesita escribir en data/.
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/www/toutadmin/data

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now toutadmin
sudo systemctl status toutadmin
journalctl -u toutadmin -f

Las cinco líneas de endurecimiento no son decorativas: ProtectSystem=strict deja todo el sistema de archivos no escribible, y ReadWritePaths reabre la única carpeta que debe serlo. Un fallo de escritura arbitraria alcanza entonces solo a data/.

5. El proxy inverso#

No sirva nunca el puerto 3000 directamente en internet: no hace TLS y no tiene por qué aprender a hacerlo.

server {
    listen 443 ssl http2;
    server_name intranet.ejemplo.com;

    ssl_certificate     /etc/letsencrypt/live/intranet.ejemplo.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/intranet.ejemplo.com/privkey.pem;

    client_max_body_size 20M;          # depósitos en la caja fuerte, documentos recibidos

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name intranet.ejemplo.com;
    return 301 https://$host$request_uri;
}

Haga además que el servicio escuche solo en el bucle local, para que el puerto 3000 sea alcanzable únicamente por el proxy.

TLS#

sudo certbot --nginx -d intranet.ejemplo.com

El producto pone la cabecera Strict-Transport-Security en cuanto la conexión está cifrada, y nunca en claro: anunciada desde una página sin cifrar no se leería, y encerraría una prueba local en https durante seis meses.

TRUST_PROXY va con el proxy, y solo con él

Esta variable hace que el servidor se crea la dirección de origen anunciada en una cabecera. Detrás de nginx es lo que hace falta: si no, todas las peticiones parecen venir de 127.0.0.1 y los límites por dirección ya no protegen nada. Sin proxy delante es al revés: cualquiera anuncia la dirección que quiera y esquiva los límites.

6. El asistente#

Abra su dominio. Mientras no exista ninguna cuenta, toda dirección redirige a /installation. Cinco pasos:

  1. El idioma de la instancia, entre los 16.
  2. Los requisitos, comprobados y mostrados.
  3. La organización: el nombre que se muestra en todas partes.
  4. Las vacaciones anuales atribuidas a cada nuevo empleado no autónomo.
  5. La cuenta de administración: dirección y contraseña de al menos doce caracteres.

En cuanto existe una cuenta, /installation redirige a la página de acceso: el asistente se ha cerrado solo, no hay ningún archivo que borrar a mano.

7. El barrido periódico#

A diferencia de la edición PHP, no hay nada que programar: el servidor lleva su propio planificador, que se despierta cada hora y hace ocho cosas.

Qué hace el barridoGarantía
Cierra las cuentas cuyo contrato ha vencidoComprobado también al entrar y al abrir el panel
Emite las facturas de las suscripciones vencidasUn índice único impide facturar dos veces
Convierte los vencimientos en notificacionesUna clave de deduplicación: un vencimiento avisa una vez
Purga las notificaciones leídas y el registro de auditoríaSegún la conservación elegida
Recoge el buzón contable (IMAP)Si la recogida está configurada
Vacía la cola de webhooks, con sus reintentosCinco intentos, luego abandono
Crea la copia automática y la externalizaCuando ha transcurrido el intervalo

Cada operación es idempotente: un barrido repetido no factura dos veces ni notifica dos veces. Es lo que permite reiniciar el servicio en cualquier momento sin pensarlo.

8. Comprobar que todo funciona#

# las pruebas (no escriben en su base)
npm test

# una instancia de demostración, para dar una vuelta
node scripts/seed-demo.js

Luego, en la interfaz: cree un miembro, entre con él, deposite un adjunto, lance una copia manual y verifíquela desde la pantalla Copias de seguridad. Esos cuatro gestos tocan la base, los archivos, los permisos y el archivo comprimido.

9. Actualizar#

# 1. una copia de seguridad primero, siempre — desde la pantalla Copias de seguridad

# 2. el código
cd /var/www/toutadmin
git pull
npm ci --omit=dev

# 3. reiniciar; la base se actualiza sola al arrancar
sudo systemctl restart toutadmin
journalctl -u toutadmin -n 30 --no-pager

El esquema evoluciona por migraciones idempotentes: repetir la actualización no rompe nada. data/ y .env no se tocan nunca.

Volver atrás#

Devuelva el código a la versión anterior y reinicie. Las migraciones no borran columnas: una base migrada sigue siendo legible por la versión anterior, salvo mención explícita en el registro de versiones. En caso de duda, restaure el archivo tomado en el paso 1.

10. Hacer copias de verdad#

El archivo tar.gz lleva la base y las cinco carpetas de archivos. La base se copia mediante la copia en línea de SQLite, que produce una copia coherente incluso durante una escritura —la edición PHP obtiene el mismo resultado mediante VACUUM INTO—. Cada archivo lleva su huella SHA-256, verificada al restaurar.

Un archivo que se queda en el servidor que protege no protege nada: configure un destino remoto (FTPS o Google Drive) desde la pantalla Copias de seguridad, y suscriba un webhook a sauvegarde.echec para saber cuándo falla la externalización. El detalle está en la página Copias de seguridad y restauración.

Probarlo primero en local#

npm install
cp .env.example .env
npm run dev        # reinicia a cada modificación

Abra http://localhost:3000. En local, deje NODE_ENV vacío: en production, las cookies se marcan como «seguras» y no se conservarán en una conexión en claro; daría vueltas en la página de acceso.

Pasar a la edición PHP, o venir de ella#

Las dos ediciones comparten el mismo esquema —142 tablas, 1311 columnas— y el mismo formato de contraseña. Detenga una, copie app.sqlite y las carpetas de archivos, arranque la otra: no hay conversión. Véase Las dos ediciones.

Documentación de Toutadmin, construida el 2026-09-13. Sitio autónomo, independiente del software.