feat: instalador para servidor nuevo + fix migracion 006 en BD vacia
scripts/install.sh automatiza el primer despliegue en un servidor Debian/Ubuntu nuevo: verifica requisitos (Node.js, npm, cliente mysql, systemd, espacio, puertos libres), pregunta dominio, puertos y base de datos (MariaDB local auto-instalada o un servidor existente), genera backend/.env con secretos aleatorios, corre migraciones + catalogos, compila el frontend y crea/arranca los servicios systemd. No crea usuarios: el primer registro publico se vuelve superadmin solo (bootstrap ya existente). Probando el instalador de punta a punta contra una base de datos recien creada goo un bug real: la migracion 006_seed_it_baseline.sql (datos de ejemplo especificos de LinkedCorp) fallaba por foreign key al no existir todavia catalogos ni usuario. Corregido para que detecte esa situacion y quede inerte en instalaciones nuevas, sin tocar el comportamiento ya probado en dev/prod. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
# Changelog - project.linkedcorp.net
|
||||
|
||||
## [1.15.1] - 2026-09-15
|
||||
### Added
|
||||
- `scripts/install.sh`: instalador para desplegar en un servidor nuevo. Verifica requisitos
|
||||
(Node.js, npm, cliente mysql, systemd, espacio, puertos), pregunta dominio/puertos y base de
|
||||
datos (MariaDB local auto-instalada o un servidor existente), genera `backend/.env` con
|
||||
secretos aleatorios, corre migraciones + catálogos, compila el frontend y crea/arranca los
|
||||
servicios systemd. No crea ningún usuario — el primer registro público se vuelve
|
||||
superadministrador solo (bootstrap ya existente).
|
||||
### Fixed
|
||||
- Migración `006_seed_it_baseline.sql` fallaba (error de foreign key) al correr sobre una base de
|
||||
datos nueva/vacía, porque insertaba proyectos de ejemplo específicos de LinkedCorp asumiendo
|
||||
catálogos y un usuario ya existentes. Encontrado probando el instalador de punta a punta contra
|
||||
una BD recién creada. Ahora detecta si esos prerequisitos no existen (instalación nueva, otra
|
||||
empresa) y queda inerte para siempre ahí, sin afectar el comportamiento ya probado en dev/prod
|
||||
(donde el marcador de "ya aplicado" sigue intacto).
|
||||
|
||||
## [1.15.0] - 2026-09-15
|
||||
### Added
|
||||
- Marca de la empresa configurable: nombre y logo editables desde Configuración → Empresa (solo
|
||||
|
||||
@@ -4,6 +4,30 @@ Arquetipo B (dos servicios separados). Primer deploy realizado el 2026-09-06.
|
||||
|
||||
---
|
||||
|
||||
## Instalación automatizada en un servidor NUEVO
|
||||
|
||||
`scripts/install.sh` automatiza todo lo de esta sección para un servidor Debian/Ubuntu nuevo:
|
||||
verifica requisitos (Node.js, npm, cliente mysql, systemd, espacio, puertos libres), pregunta
|
||||
dominio/puertos y datos de base de datos (MariaDB local — se instala sola si hace falta — o un
|
||||
servidor existente), crea la base + usuario, genera `backend/.env` con secretos aleatorios,
|
||||
instala dependencias, corre migraciones + catálogos, compila el frontend y crea/arranca los
|
||||
servicios systemd.
|
||||
|
||||
```bash
|
||||
# En el servidor nuevo, ya con el repo clonado en /opt/apps/project.linkedcorp.net/source:
|
||||
cd /opt/apps/project.linkedcorp.net/source
|
||||
sudo scripts/install.sh
|
||||
```
|
||||
|
||||
No configura el reverse proxy/TLS (paso 11 más abajo, específico de cada proveedor) ni crea
|
||||
ningún usuario: el primer registro público en `/registro` se vuelve superadministrador solo — no
|
||||
hace falta `create-admin.js` en instalaciones nuevas.
|
||||
|
||||
Las secciones de abajo son la referencia manual (lo que hace el script paso a paso) — útiles para
|
||||
entender el porqué de cada cosa o para resolver un problema puntual sin volver a correr todo.
|
||||
|
||||
---
|
||||
|
||||
## Primer despliegue en producción (ya ejecutado — referencia)
|
||||
|
||||
### 1. Entrar al servidor
|
||||
|
||||
@@ -6,159 +6,171 @@ SET NAMES utf8mb4;
|
||||
|
||||
SET @already := (SELECT COUNT(*) FROM system_settings WHERE `key` = 'seed_it_baseline_2026_09');
|
||||
|
||||
SET @sql := IF(@already = 0, 'DELETE FROM comments WHERE entity_type IN (\'project\',\'activity\')', 'SELECT 1');
|
||||
-- Instalacion nueva (otra empresa/servidor, catalogos y usuario 1 recien creados por
|
||||
-- migrate-db.js + seed-db.js, todavia sin correr) — esta migracion es un fix de datos puntual
|
||||
-- de la base de LinkedCorp, no algo que deba aplicarse en ningun otro lado. Si faltan estos
|
||||
-- prerrequisitos, @should_seed queda en 0 y el marcador se escribe igual mas abajo para que
|
||||
-- quede inerte para siempre en esa instalacion (no reintenta en cada migrate futuro).
|
||||
SET @prereqs_ok := (
|
||||
(SELECT COUNT(*) FROM users) > 0
|
||||
AND (SELECT COUNT(*) FROM priorities WHERE id = 2) > 0
|
||||
AND (SELECT COUNT(*) FROM project_statuses WHERE id = 1) > 0
|
||||
);
|
||||
SET @should_seed := (@already = 0 AND @prereqs_ok);
|
||||
|
||||
SET @sql := IF(@should_seed, 'DELETE FROM comments WHERE entity_type IN (\'project\',\'activity\')', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'DELETE FROM attachments WHERE entity_type IN (\'project\',\'activity\')', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'DELETE FROM attachments WHERE entity_type IN (\'project\',\'activity\')', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'DELETE FROM notifications WHERE entity_type IN (\'project\',\'activity\')', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'DELETE FROM notifications WHERE entity_type IN (\'project\',\'activity\')', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'DELETE FROM projects', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'DELETE FROM projects', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'ALTER TABLE projects AUTO_INCREMENT = 1', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'ALTER TABLE projects AUTO_INCREMENT = 1', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'ALTER TABLE activities AUTO_INCREMENT = 1', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'ALTER TABLE activities AUTO_INCREMENT = 1', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'DELETE FROM users WHERE email = \'pflores@hotmail.es\'', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'DELETE FROM users WHERE email = \'pflores@hotmail.es\'', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Proyecto: Cumplimiento legal y proteccion de datos
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Cumplimiento legal y proteccion de datos\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Cumplimiento legal y proteccion de datos\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @p := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Implementar LOPD/LODPD\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Implementar LOPD/LODPD\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Politica de cookies\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Politica de cookies\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Cumplimiento WhatsApp Business\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Cumplimiento WhatsApp Business\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Leyendas obligatorias en facturas\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Leyendas obligatorias en facturas\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Proyecto: Estandarizar correos corporativos
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Estandarizar correos corporativos\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Estandarizar correos corporativos\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @p := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Cambiar los correos al nuevo formato (inicial + apellido, 2da inicial si duplica)\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Cambiar los correos al nuevo formato (inicial + apellido, 2da inicial si duplica)\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Proyecto: Gestion de equipos y activos de TI
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Gestion de equipos y activos de TI\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Gestion de equipos y activos de TI\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @p := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Inventario de equipos (codigos / sistema de activos)\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Inventario de equipos (codigos / sistema de activos)\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Revision periodica de equipos\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Revision periodica de equipos\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Limpieza y mejoras de equipos\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Limpieza y mejoras de equipos\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Respuestas automaticas de correo cuando alguien sale de la empresa\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Respuestas automaticas de correo cuando alguien sale de la empresa\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Proyecto: Gestion de credenciales y licencias
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Gestion de credenciales y licencias\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Gestion de credenciales y licencias\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @p := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Archivo general de claves de sistemas y usuarios (BitWarden)\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Archivo general de claves de sistemas y usuarios (BitWarden)\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Inventario de licencias de equipos\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Inventario de licencias de equipos\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Proyecto: Infraestructura y redes
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Infraestructura y redes\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Infraestructura y redes\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @p := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Redes con el proveedor\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Redes con el proveedor\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Server\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Server\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Controlar el acceso a internet\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Controlar el acceso a internet\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Mejoras continuas de la red\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Mejoras continuas de la red\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- Proyecto: Mejoras al sistema SAGA
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Mejoras al sistema SAGA\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO projects (code, name, priority_id, status_id, owner_user_id, risk_level, created_by) VALUES (\'PENDING\', \'Mejoras al sistema SAGA\', 2, 1, 1, \'low\', 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @p := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE projects SET code = CONCAT(\'PRY-\', LPAD(@p,4,\'0\')) WHERE id = @p', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @sql := IF(@already = 0, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT IGNORE INTO project_members (project_id, user_id, role_in_project) VALUES (@p, 1, \'owner\')', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Sistematizar mejoras en el sistema de SAGA\', 2, 1, 1)', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'INSERT INTO activities (project_id, code, name, priority_id, status_id, created_by) VALUES (@p, \'PENDING\', \'Sistematizar mejoras en el sistema de SAGA\', 2, 1, 1)', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
SET @a := LAST_INSERT_ID();
|
||||
SET @sql := IF(@already = 0, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
SET @sql := IF(@should_seed, 'UPDATE activities SET code = CONCAT(\'ACT-\', LPAD(@a,4,\'0\')) WHERE id = @a', 'SELECT 1');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
SET @sql := IF(@already = 0, 'INSERT INTO system_settings (`key`, `value`) VALUES (\'seed_it_baseline_2026_09\', NOW())', 'SELECT 1');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "project-linkedcorp-backend",
|
||||
"version": "1.15.0",
|
||||
"version": "1.15.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "src/server.js",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "project-linkedcorp-frontend",
|
||||
"private": true,
|
||||
"version": "1.15.0",
|
||||
"version": "1.15.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
Executable
+389
@@ -0,0 +1,389 @@
|
||||
#!/usr/bin/env bash
|
||||
# Instalador de project.linkedcorp.net para un servidor NUEVO (Debian/Ubuntu, arquetipo B:
|
||||
# backend + frontend como dos servicios systemd separados).
|
||||
#
|
||||
# Uso: cloná el repo en /opt/apps/project.linkedcorp.net/source (ver DEPLOY_WEB.md, paso
|
||||
# "Clonar el repositorio") y desde ahi corré:
|
||||
#
|
||||
# sudo scripts/install.sh
|
||||
#
|
||||
# Que hace:
|
||||
# 1. Verifica requisitos (Node.js, npm, cliente mysql, systemd, espacio, puertos libres).
|
||||
# 2. Pregunta dominio, puertos, y datos de base de datos: MariaDB local (se instala sola si
|
||||
# hace falta) o un servidor MySQL/MariaDB existente.
|
||||
# 3. Crea la base de datos + usuario de la app (clave aleatoria), genera backend/.env con
|
||||
# todos los secretos generados al azar (JWT_SECRET, ENCRYPTION_KEY).
|
||||
# 4. Instala dependencias, compila el frontend, corre migraciones + catalogos base.
|
||||
# 5. Crea y arranca los dos servicios systemd, verifica que respondan.
|
||||
#
|
||||
# Que NO hace (queda manual, es especifico de cada servidor/proveedor):
|
||||
# - Reverse proxy + certificado TLS hacia el dominio publico (ver DEPLOY_WEB.md, paso
|
||||
# "Configurar Nginx Proxy Manager").
|
||||
# - Crear un usuario de la app: el primer registro publico en /registro se vuelve
|
||||
# superadministrador automaticamente (bootstrap ya implementado en el backend) — no hace
|
||||
# falta ningun paso extra ni script aparte.
|
||||
set -euo pipefail
|
||||
|
||||
APP_NAME="project.linkedcorp.net"
|
||||
SVC_BACKEND="project-linkedcorp-backend"
|
||||
SVC_FRONTEND="project-linkedcorp-frontend"
|
||||
MIN_NODE_MAJOR=20
|
||||
MIN_FREE_MB=1024
|
||||
|
||||
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
C_OK="\033[32m"; C_FAIL="\033[31m"; C_WARN="\033[33m"; C_RESET="\033[0m"
|
||||
FAILED=0
|
||||
ok() { echo -e " ${C_OK}OK${C_RESET} $1"; }
|
||||
fail() { echo -e " ${C_FAIL}FALTA${C_RESET} $1"; FAILED=1; }
|
||||
warn() { echo -e " ${C_WARN}AVISO${C_RESET} $1"; }
|
||||
|
||||
require_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "Este instalador necesita privilegios de root (instala paquetes, crea servicios systemd)."
|
||||
echo "Ejecuta: sudo $0"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------- 1. verificacion de requisitos ----------
|
||||
check_requirements() {
|
||||
echo "== Verificando requisitos =="
|
||||
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
local major
|
||||
major="$(node -v | sed 's/^v//' | cut -d. -f1)"
|
||||
if [[ "$major" -ge "$MIN_NODE_MAJOR" ]]; then
|
||||
ok "Node.js $(node -v)"
|
||||
else
|
||||
fail "Node.js $(node -v) — se requiere >= v${MIN_NODE_MAJOR}"
|
||||
fi
|
||||
else
|
||||
fail "Node.js no instalado (requiere >= v${MIN_NODE_MAJOR})"
|
||||
fi
|
||||
|
||||
command -v npm >/dev/null 2>&1 && ok "npm $(npm -v)" || fail "npm no instalado"
|
||||
command -v mysql >/dev/null 2>&1 && ok "cliente mysql" || fail "cliente mysql no instalado (paquete mariadb-client)"
|
||||
command -v systemctl >/dev/null 2>&1 && ok "systemd" || fail "systemd no disponible (este instalador lo necesita)"
|
||||
command -v openssl >/dev/null 2>&1 && ok "openssl" || fail "openssl no instalado (necesario para generar secretos)"
|
||||
command -v curl >/dev/null 2>&1 && ok "curl" || fail "curl no instalado (necesario para verificar al final)"
|
||||
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
ok "apt-get disponible"
|
||||
else
|
||||
warn "apt-get no disponible — si eliges 'MariaDB local' vas a tener que instalarla vos antes de continuar"
|
||||
fi
|
||||
|
||||
local avail_kb
|
||||
avail_kb="$(df -Pk /opt 2>/dev/null | tail -1 | awk '{print $4}')"
|
||||
if [[ -n "$avail_kb" && "$avail_kb" -ge $((MIN_FREE_MB * 1024)) ]]; then
|
||||
ok "espacio libre en /opt: $((avail_kb / 1024)) MB"
|
||||
else
|
||||
fail "menos de ${MIN_FREE_MB}MB libres en /opt"
|
||||
fi
|
||||
|
||||
if [[ -f "$SOURCE_DIR/backend/package.json" && -f "$SOURCE_DIR/frontend/package.json" ]]; then
|
||||
ok "repo detectado en $SOURCE_DIR"
|
||||
else
|
||||
fail "no se encontro backend/frontend en $SOURCE_DIR — corre este script desde la raiz del repo ya clonado"
|
||||
fi
|
||||
|
||||
echo
|
||||
if [[ "$FAILED" -ne 0 ]]; then
|
||||
echo "Faltan requisitos — resuelve lo anterior antes de continuar."
|
||||
exit 1
|
||||
fi
|
||||
echo "Requisitos OK."
|
||||
echo
|
||||
}
|
||||
|
||||
port_free() {
|
||||
local port="$1"
|
||||
! ss -ltn 2>/dev/null | awk '{print $4}' | grep -qE "[.:]${port}\$"
|
||||
}
|
||||
|
||||
check_ports() {
|
||||
echo "== Verificando puertos =="
|
||||
local bad=0
|
||||
for p in "$BACKEND_PORT" "$FRONTEND_PORT"; do
|
||||
if port_free "$p"; then
|
||||
ok "puerto $p libre"
|
||||
else
|
||||
echo -e " ${C_FAIL}OCUPADO${C_RESET} puerto $p"
|
||||
bad=1
|
||||
fi
|
||||
done
|
||||
echo
|
||||
if [[ "$bad" -ne 0 ]]; then
|
||||
echo "Elige otro puerto o libera el que esta ocupado, y vuelve a correr el instalador."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------- 2. configuracion interactiva ----------
|
||||
ask() {
|
||||
local prompt="$1" default="$2" var
|
||||
read -r -p "$prompt [$default]: " var
|
||||
echo "${var:-$default}"
|
||||
}
|
||||
|
||||
ensure_run_user() {
|
||||
if id "$RUN_USER" >/dev/null 2>&1; then
|
||||
ok "usuario '$RUN_USER' ya existe"
|
||||
else
|
||||
echo "El usuario '$RUN_USER' no existe — se crea ahora (sistema, sin login)."
|
||||
useradd --system --create-home --shell /usr/sbin/nologin "$RUN_USER"
|
||||
ok "usuario '$RUN_USER' creado"
|
||||
fi
|
||||
}
|
||||
|
||||
gather_config() {
|
||||
echo "== Configuracion =="
|
||||
APP_ROOT="$(ask "Directorio de instalacion" "/opt/apps/${APP_NAME}")"
|
||||
DOMAIN="$(ask "Dominio publico (URL completa, para CORS)" "https://${APP_NAME}")"
|
||||
BACKEND_PORT="$(ask "Puerto del backend" "4019")"
|
||||
FRONTEND_PORT="$(ask "Puerto del frontend" "5196")"
|
||||
RUN_USER="$(ask "Usuario del sistema que corre los servicios" "${SUDO_USER:-deploy}")"
|
||||
echo
|
||||
ensure_run_user
|
||||
echo
|
||||
check_ports
|
||||
|
||||
echo "Base de datos:"
|
||||
echo " 1) MariaDB local (se instala en este mismo servidor si hace falta)"
|
||||
echo " 2) Servidor MySQL/MariaDB existente (otra IP)"
|
||||
local db_choice
|
||||
db_choice="$(ask "Elige una opcion" "1")"
|
||||
|
||||
DB_NAME="$(ask "Nombre de la base de datos" "project_linkedcorp")"
|
||||
DB_APP_USER="$(ask "Usuario de la app para la BD" "project_linkedcorp")"
|
||||
|
||||
if [[ "$db_choice" == "2" ]]; then
|
||||
DB_LOCAL=0
|
||||
DB_HOST="$(ask "Host de la base de datos" "")"
|
||||
DB_PORT="$(ask "Puerto" "3306")"
|
||||
DB_ADMIN_USER="$(ask "Usuario admin de esa BD (solo para crear la BD/usuario — no se guarda)" "root")"
|
||||
read -r -s -p "Clave del usuario admin: " DB_ADMIN_PASSWORD; echo
|
||||
GRANT_HOST="$(ask "IP de este servidor (para el GRANT — desde donde se conecta la app)" "$(hostname -I 2>/dev/null | awk '{print $1}')")"
|
||||
else
|
||||
DB_LOCAL=1
|
||||
DB_HOST="127.0.0.1"
|
||||
DB_PORT="3306"
|
||||
GRANT_HOST="127.0.0.1"
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
# ---------- 3. base de datos ----------
|
||||
setup_local_mariadb() {
|
||||
if command -v mysql >/dev/null 2>&1 && systemctl is-active --quiet mariadb 2>/dev/null; then
|
||||
ok "MariaDB ya esta instalada y corriendo"
|
||||
return
|
||||
fi
|
||||
echo "Instalando MariaDB local..."
|
||||
apt-get update -y
|
||||
apt-get install -y mariadb-server
|
||||
systemctl enable --now mariadb
|
||||
ok "MariaDB instalada y arrancada"
|
||||
}
|
||||
|
||||
mysql_admin() {
|
||||
# Ejecuta el SQL que llega por stdin como admin. Local: root via socket (sin clave, tipico en
|
||||
# Debian/Ubuntu con auth_socket). Remoto: con host/usuario/clave provistos.
|
||||
if [[ "$DB_LOCAL" -eq 1 ]]; then
|
||||
mysql -u root
|
||||
else
|
||||
MYSQL_PWD="$DB_ADMIN_PASSWORD" mysql -h "$DB_HOST" -P "$DB_PORT" -u "$DB_ADMIN_USER"
|
||||
fi
|
||||
}
|
||||
|
||||
create_database() {
|
||||
echo "== Base de datos =="
|
||||
[[ "$DB_LOCAL" -eq 1 ]] && setup_local_mariadb
|
||||
|
||||
DB_APP_PASSWORD="$(openssl rand -base64 18 | tr -d '/+=' | cut -c1-20)"
|
||||
|
||||
if ! mysql_admin <<SQL
|
||||
CREATE DATABASE IF NOT EXISTS \`${DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
CREATE USER IF NOT EXISTS '${DB_APP_USER}'@'${GRANT_HOST}' IDENTIFIED BY '${DB_APP_PASSWORD}';
|
||||
GRANT ALL PRIVILEGES ON \`${DB_NAME}\`.* TO '${DB_APP_USER}'@'${GRANT_HOST}';
|
||||
FLUSH PRIVILEGES;
|
||||
SQL
|
||||
then
|
||||
echo "No se pudo conectar/crear la base de datos con esas credenciales admin."
|
||||
exit 1
|
||||
fi
|
||||
ok "base '${DB_NAME}' y usuario '${DB_APP_USER}'@'${GRANT_HOST}' listos"
|
||||
echo
|
||||
}
|
||||
|
||||
# ---------- 4. estructura + .env ----------
|
||||
setup_tree() {
|
||||
echo "== Directorios =="
|
||||
mkdir -p "$APP_ROOT"/{backend,frontend,storage,logs}
|
||||
chown -R "$RUN_USER":"$RUN_USER" "$APP_ROOT"
|
||||
ok "$APP_ROOT/{backend,frontend,storage,logs}"
|
||||
echo
|
||||
}
|
||||
|
||||
write_env() {
|
||||
echo "== backend/.env =="
|
||||
local jwt_secret enc_key
|
||||
jwt_secret="$(openssl rand -hex 32)"
|
||||
enc_key="$(openssl rand -hex 32)"
|
||||
|
||||
cat > "$APP_ROOT/backend/.env" <<EOF
|
||||
PORT=${BACKEND_PORT}
|
||||
NODE_ENV=production
|
||||
DB_HOST=${DB_HOST}
|
||||
DB_PORT=${DB_PORT}
|
||||
DB_NAME=${DB_NAME}
|
||||
DB_USER=${DB_APP_USER}
|
||||
DB_PASSWORD=${DB_APP_PASSWORD}
|
||||
JWT_SECRET=${jwt_secret}
|
||||
JWT_EXPIRES=8h
|
||||
ENCRYPTION_KEY=${enc_key}
|
||||
CORS_ORIGIN=${DOMAIN}
|
||||
STORAGE_ROOT=${APP_ROOT}/storage
|
||||
EOF
|
||||
chown "$RUN_USER":"$RUN_USER" "$APP_ROOT/backend/.env"
|
||||
chmod 640 "$APP_ROOT/backend/.env"
|
||||
ok "generado (secretos aleatorios, permisos 640)"
|
||||
echo
|
||||
}
|
||||
|
||||
# ---------- 5. dependencias, migraciones, build ----------
|
||||
install_backend() {
|
||||
echo "== Backend =="
|
||||
# El servicio corre DESDE source/backend (ver write_services) — es donde tienen que vivir
|
||||
# los node_modules, no en el runtime de $APP_ROOT (que solo guarda el .env persistente).
|
||||
(cd "$SOURCE_DIR/backend" && npm install --omit=dev)
|
||||
ok "dependencias instaladas"
|
||||
|
||||
echo "Aplicando migraciones y catalogos base..."
|
||||
(
|
||||
cd "$SOURCE_DIR/backend"
|
||||
set -a; source "$APP_ROOT/backend/.env"; set +a
|
||||
node src/scripts/migrate-db.js
|
||||
node src/scripts/seed-db.js
|
||||
)
|
||||
ok "migraciones y catalogos aplicados"
|
||||
echo
|
||||
}
|
||||
|
||||
build_frontend() {
|
||||
echo "== Frontend =="
|
||||
(cd "$SOURCE_DIR/frontend" && npm install && npm run build)
|
||||
rm -rf "$APP_ROOT/frontend/dist"
|
||||
cp -r "$SOURCE_DIR/frontend/dist" "$APP_ROOT/frontend/dist"
|
||||
|
||||
cat > "$APP_ROOT/frontend/package.json" <<'EOF'
|
||||
{ "name": "project-linkedcorp-frontend-runtime", "private": true, "version": "1.0.0",
|
||||
"dependencies": { "serve": "^14.2.6" } }
|
||||
EOF
|
||||
(cd "$APP_ROOT/frontend" && npm install --omit=dev)
|
||||
chown -R "$RUN_USER":"$RUN_USER" "$APP_ROOT/frontend"
|
||||
ok "compilado y publicado en $APP_ROOT/frontend"
|
||||
echo
|
||||
}
|
||||
|
||||
# ---------- 6. systemd ----------
|
||||
write_services() {
|
||||
echo "== Servicios systemd =="
|
||||
cat > "/etc/systemd/system/${SVC_BACKEND}.service" <<EOF
|
||||
[Unit]
|
||||
Description=${APP_NAME} backend
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${RUN_USER}
|
||||
WorkingDirectory=${SOURCE_DIR}/backend
|
||||
ExecStart=/usr/bin/node src/server.js
|
||||
EnvironmentFile=${APP_ROOT}/backend/.env
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StandardOutput=append:${APP_ROOT}/logs/backend.log
|
||||
StandardError=append:${APP_ROOT}/logs/backend-error.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
cat > "/etc/systemd/system/${SVC_FRONTEND}.service" <<EOF
|
||||
[Unit]
|
||||
Description=${APP_NAME} frontend
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=${RUN_USER}
|
||||
WorkingDirectory=${APP_ROOT}/frontend
|
||||
ExecStart=/usr/bin/node node_modules/.bin/serve -s dist -l ${FRONTEND_PORT}
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
StandardOutput=append:${APP_ROOT}/logs/frontend.log
|
||||
StandardError=append:${APP_ROOT}/logs/frontend-error.log
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable --now "$SVC_BACKEND" "$SVC_FRONTEND"
|
||||
ok "servicios creados y arrancados"
|
||||
echo
|
||||
}
|
||||
|
||||
verify() {
|
||||
echo "== Verificacion =="
|
||||
sleep 2
|
||||
if curl -sf "http://127.0.0.1:${BACKEND_PORT}/health" >/dev/null; then
|
||||
ok "backend responde en :${BACKEND_PORT}"
|
||||
else
|
||||
warn "backend no responde todavia — revisa: journalctl -u ${SVC_BACKEND} -n 50"
|
||||
fi
|
||||
if curl -sfo /dev/null "http://127.0.0.1:${FRONTEND_PORT}/"; then
|
||||
ok "frontend responde en :${FRONTEND_PORT}"
|
||||
else
|
||||
warn "frontend no responde todavia — revisa: journalctl -u ${SVC_FRONTEND} -n 50"
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
summary() {
|
||||
echo "======================================================"
|
||||
echo " Instalacion lista"
|
||||
echo "======================================================"
|
||||
echo " Codigo: ${SOURCE_DIR}"
|
||||
echo " Runtime: ${APP_ROOT}"
|
||||
echo " Backend: http://127.0.0.1:${BACKEND_PORT} (systemctl status ${SVC_BACKEND})"
|
||||
echo " Frontend: http://127.0.0.1:${FRONTEND_PORT} (systemctl status ${SVC_FRONTEND})"
|
||||
echo
|
||||
echo " Falta (manual, fuera del alcance de este instalador):"
|
||||
echo " - Reverse proxy + TLS hacia ${DOMAIN} (ver DEPLOY_WEB.md, 'Nginx Proxy Manager'):"
|
||||
echo " / -> puerto ${FRONTEND_PORT}"
|
||||
echo " /api -> puerto ${BACKEND_PORT}"
|
||||
echo " - Entra a ${DOMAIN}/registro y crea el primer usuario: se vuelve"
|
||||
echo " superadministrador automaticamente, sin pasos extra."
|
||||
echo " - Configura el correo SMTP desde Configuracion una vez logueado (opcional)."
|
||||
echo " - Opcional: find ${SOURCE_DIR} -type f -iname '*.md' -delete (limpia la documentacion"
|
||||
echo " del checkout en el servidor de produccion)."
|
||||
echo "======================================================"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_root
|
||||
check_requirements
|
||||
gather_config
|
||||
create_database
|
||||
setup_tree
|
||||
write_env
|
||||
install_backend
|
||||
build_frontend
|
||||
write_services
|
||||
verify
|
||||
summary
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user