feat: actualizar install.sh para Proxmox LXC con venv, snapshot classrooms_db.sql, backup_db.sh y fix linter en occupancy

This commit is contained in:
Carlos Tello
2026-09-04 22:56:53 -03:00
parent e0c58409cd
commit eea7adb1d0
7 changed files with 2639 additions and 441 deletions
+33 -8
View File
@@ -1,6 +1,6 @@
# Resumen de Cambios - Sesión de Desarrollo (Rama `dev`) # Resumen de Cambios - Sesión de Desarrollo (Rama `dev`)
**Fecha:** 2026-09-02 / 2026-09-03 **Fecha:** 2026-09-02 / 2026-09-03 / 2026-09-04
**Proyecto:** Admin Edu-Space (Flask + PostgreSQL) **Proyecto:** Admin Edu-Space (Flask + PostgreSQL)
**Rama:** `dev` **Rama:** `dev`
@@ -345,14 +345,39 @@
* **Calendario Semanal:** Agregado el filtro **Filtrar por Edificio** en la barra superior junto al filtro de Piso. * **Calendario Semanal:** Agregado el filtro **Filtrar por Edificio** en la barra superior junto al filtro de Piso.
* **Modal Estilo Google Calendar:** La sección de espacio físico desglosa limpiamente en badges: 🏢 Edificio, 🪜 Piso y 🚪 Aula. * **Modal Estilo Google Calendar:** La sección de espacio físico desglosa limpiamente en badges: 🏢 Edificio, 🪜 Piso y 🚪 Aula.
---
## 21. Corrección de Linter y Renderizado de Barras en Métricas de Ocupación (2026-09-04)
* **Plantilla de Métricas de Ocupación ([app/templates/admin/metrics/occupancy.html](file:///c:/Workspace/admin-edu-space/app/templates/admin/metrics/occupancy.html)):**
* **Eliminación de Falsos Positivos de CSS en el IDE:** Se resolvieron los 12 errores de sintaxis (`property value expected`, `at-rule or selector expected`) reportados por el analizador HTML/CSS del IDE en las tarjetas de KPI (Tasa de Ocupación Hoy, Semanal, Mensual) y en la distribución por Piso / Nivel.
* **Atributos Semánticos HTML5:** Se sustituyó la inyección directa de Jinja2 en atributos `style="width: {{ ... }}%;"` por el atributo de datos estándar `data-width="{{ ... }}%"`.
* **Inicialización y Animación Dinámica en JS:**
* Se configuró en CSS (`.metric-bar-fill { width: 0; }`) un ancho base inicial.
* Se implementó un script en el bloque `extra_js` que asigna el ancho dinámico al cargar el DOM (`DOMContentLoaded`).
* En conjunto con `transition: width 0.6s ease;`, las barras se expanden de forma fluida y visualmente atractiva al cargar la pantalla.
---
## 22. Actualización de Instalador, Backup de Base de Datos y Despliegue en Proxmox LXC (2026-09-04)
* **Backup de Base de Datos ([classrooms_db.sql](file:///c:/Workspace/admin-edu-space/classrooms_db.sql)):**
* **Extracción del Snapshot Completo:** Volcado íntegro de la base de datos PostgreSQL activa (`classrooms_db`) que alimenta el sistema en producción/local.
* **Contenido Respaldado:** 25 aulas físicas y virtuales, 206 comisiones, 206 asignaturas, 412 reservas, 24 carreras, 12 tipos de hitos, matrices de permisos RBAC y usuarios administradores.
* **Compatibilidad Multi-Versión:** Se sanitizó la directiva `\restrict` específica de PostgreSQL 18 para garantizar compatibilidad nativa con PostgreSQL 14, 15, 16, 17 y 18.
* **Actualización del Instalador del Sistema para Proxmox LXC ([install.sh](file:///c:/Workspace/admin-edu-space/install.sh)):**
* **Optimización Proxmox LXC:** Compatible con contenedores Debian 11/12 y Ubuntu 20.04/22.04/24.04 en Proxmox VE.
* **Migración Automática de Directorio:** Si el repositorio se clona en `/root`, se traslada automáticamente a `/opt/admin-edu-space` para garantizar permisos de ejecución del usuario del servicio (`eduspace`).
* **Despliegue del Motor PostgreSQL Nativo:** Instalación desatendida del motor en el LXC, inicialización dinámica del servicio y bucle de healthcheck de conexión.
* **Restauración Automatizada y Permisos:** Detección automática de `classrooms_db.sql`, recreación limpia, asignación de permisos `GRANT ALL` en el esquema `public`, todas las tablas y secuencias al usuario `eduspace_user`.
* **Entorno Virtual Nativo (`venv`):** Configuración en `/opt/admin-edu-space/venv` con dependencias del sistema y `gunicorn`.
* **Servicio Systemd de Producción:** Configuración y activación de `admin-edu-space.service` en el puerto 5000.
* **Ejecución Idempotente:** Integración de `init_db.py` tras la restauración para validar la coherencia del esquema y roles.
* **Utilidad de Backup Continuo ([backup_db.sh](file:///c:/Workspace/admin-edu-space/backup_db.sh)):**
* Script automatizado para generar copias de seguridad fechadas en `backups/` y actualizar simultáneamente `classrooms_db.sql` para facilitar la migración entre contenedores LXC.
* **Documentación y Tutorial en README ([README.md](file:///c:/Workspace/admin-edu-space/README.md)):**
* Guía paso a paso para la creación del contenedor LXC en Proxmox VE (especificaciones de CPU, RAM, disco y habilitación de `nesting=1`).
* Instrucciones completas de instalación desatendida con `install.sh`.
* Corrección de comandos de inicio apuntando al punto de entrada oficial [wsgi.py](file:///c:/Workspace/admin-edu-space/wsgi.py).
* Guía paso a paso para la generación y restauración de backups.
* **Dependencias ([requirements.txt](file:///c:/Workspace/admin-edu-space/requirements.txt)):**
* Se incorporó formalmente `gunicorn>=21.2.0` al listado de dependencias del proyecto.
+306 -315
View File
@@ -1,315 +1,306 @@
# Panel de Administración Edu-Space (UniCABA) # Panel de Administración Edu-Space (UniCABA)
Plataforma integral de gestión áulica, asignación de espacios, control de horarios y reservas académicas para la **Universidad de la Ciudad de Buenos Aires (UniCABA)**, desarrollada con **Python / Flask**, **SQLAlchemy**, **PostgreSQL** y **Bootstrap 5**. Plataforma integral de gestión áulica, asignación de espacios, control de horarios y reservas académicas para la **Universidad de la Ciudad de Buenos Aires (UniCABA)**, desarrollada con **Python / Flask**, **SQLAlchemy**, **PostgreSQL** y **Bootstrap 5**.
--- ---
## 🏛️ Sede Institucional y Ubicaciones ## 🏛️ Sede Institucional y Ubicaciones
* **Sede Central UniCABA:** Tte. Gral. Juan Domingo Perón 802, Ciudad Autónoma de Buenos Aires (CABA) — Aulas de grado, auditorio principal y laboratorios distribuidos desde Planta Baja (Piso 0) hasta el 4.° Piso. * **Sede Central UniCABA:** Tte. Gral. Juan Domingo Perón 802, Ciudad Autónoma de Buenos Aires (CABA) — Aulas de grado, auditorio principal y laboratorios distribuidos desde Planta Baja (Piso 0) hasta el 4.° Piso.
* **Campus Virtual:** Espacio áulico digital integrado (Microsoft Teams / Moodle) configurado con **capacidad ilimitada ($\infty$)**. * **Campus Virtual:** Espacio áulico digital integrado (Microsoft Teams / Moodle) configurado con **capacidad ilimitada ($\infty$)**.
--- ---
## ✨ Características Principales ## ✨ Características Principales
### 🛡️ 1. Control de Accesos RBAC Granular (Matriz estilo FortiGate) ### 🛡️ 1. Control de Accesos RBAC Granular (Matriz estilo FortiGate)
* **Gestión por Módulos:** Permisos independientes por cada área del sistema: * **Gestión por Módulos:** Permisos independientes por cada área del sistema:
1. **Aulas y Espacios:** Creación, edición, capacidad y estado de aulas físicas y virtuales. 1. **Aulas y Espacios:** Creación, edición, capacidad y estado de aulas físicas y virtuales.
2. **Edificios y Sedes:** Administración de sedes, pisos y arquitectura edilicia. 2. **Edificios y Sedes:** Administración de sedes, pisos y arquitectura edilicia.
3. **Reservas de Aulas:** Solicitud, aprobación, confirmación y cancelación. 3. **Reservas de Aulas:** Solicitud, aprobación, confirmación y cancelación.
4. **Cartelera y Cronograma:** Calendario interactivo, visualización diaria y comisiones. 4. **Cartelera y Cronograma:** Calendario interactivo, visualización diaria y comisiones.
5. **Gestión Académica:** Carreras, asignaturas, códigos y comisiones. 5. **Gestión Académica:** Carreras, asignaturas, códigos y comisiones.
6. **Sincronización Sheets:** Importador y sincronizador de Google Sheets. 6. **Sincronización Sheets:** Importador y sincronizador de Google Sheets.
7. **Optimizador IA:** Algoritmo genético de distribución y resolución de conflictos. 7. **Optimizador IA:** Algoritmo genético de distribución y resolución de conflictos.
8. **Usuarios y Accesos RBAC:** Cuentas, roles y matriz de permisos. 8. **Usuarios y Accesos RBAC:** Cuentas, roles y matriz de permisos.
* **Niveles de Permiso Granulares:** `Ninguno`, `Solo Lectura` o `Lectura y Escritura`. * **Niveles de Permiso Granulares:** `Ninguno`, `Solo Lectura` o `Lectura y Escritura`.
* **Controles Rápidos:** Botón *"Establecer Todos"* para configuración masiva inmediata. * **Controles Rápidos:** Botón *"Establecer Todos"* para configuración masiva inmediata.
* **Roles Predefinidos:** `Admin` (control total), `Docente`, `Operador` y `Consulta`. * **Roles Predefinidos:** `Admin` (control total), `Docente`, `Operador` y `Consulta`.
### 📊 2. Sincronización e Importación desde Google Sheets ### 📊 2. Sincronización e Importación desde Google Sheets
* Integración directa con la cartelera académica de UniCABA en Google Sheets. * Integración directa con la cartelera académica de UniCABA en Google Sheets.
* Extracción e importación normalizada de los 5 campos clave: * Extracción e importación normalizada de los 5 campos clave:
* **Código Académico:** Códigos reales de asignatura (ej. `INGA0003`, `ASIG00165`). * **Código Académico:** Códigos reales de asignatura (ej. `INGA0003`, `ASIG00165`).
* **Asignatura:** Nombre oficial de la materia. * **Asignatura:** Nombre oficial de la materia.
* **Aula:** Mapeo a aulas físicas (PB a 4.° piso) o aula `VIRTUAL` ($\infty$). * **Aula:** Mapeo a aulas físicas (PB a 4.° piso) o aula `VIRTUAL` ($\infty$).
* **Carrera:** Normalización y vinculación relacional con entidades de carrera. * **Carrera:** Normalización y vinculación relacional con entidades de carrera.
* **Horarios y Turnos:** Detección automática de franjas horarias y delimitadores de bloque (`Turno Mañana` y `Turno Tarde / Vespertino`). * **Horarios y Turnos:** Detección automática de franjas horarias y delimitadores de bloque (`Turno Mañana` y `Turno Tarde / Vespertino`).
### 📅 3. Calendario Interactivo de Reservas (FullCalendar) ### 📅 3. Calendario Interactivo de Reservas (FullCalendar)
* **Visualización Semanal, Mensual y Diaria:** Grilla ágil de alta legibilidad con jerarquía visual: * **Visualización Semanal, Mensual y Diaria:** Grilla ágil de alta legibilidad con jerarquía visual:
* Asignatura destacada en tipografía grande y negrita. * Asignatura destacada en tipografía grande y negrita.
* Badges con código de materia, aula asignada, turno y franja horaria. * Badges con código de materia, aula asignada, turno y franja horaria.
* **Filtros Dinámicos Combinables:** * **Filtros Dinámicos Combinables:**
* Filtrar por **Aula**. * Filtrar por **Aula**.
* Filtrar por **Materia / Asignatura**. * Filtrar por **Materia / Asignatura**.
* Filtrar por **Carrera**. * Filtrar por **Carrera**.
* Filtrar por **Turno** (`Turno Mañana` / `Turno Tarde`). * Filtrar por **Turno** (`Turno Mañana` / `Turno Tarde`).
* Filtrar por **Piso / Planta** (`Planta Baja`, `Piso 1`, `Piso 2`, `Piso 3`, `Piso 4`). * Filtrar por **Piso / Planta** (`Planta Baja`, `Piso 1`, `Piso 2`, `Piso 3`, `Piso 4`).
* **Switch "Ocultar Aulas Virtuales":** Aísla la ocupación física real en el edificio central excluyendo los eventos virtuales de capacidad infinita. * **Switch "Ocultar Aulas Virtuales":** Aísla la ocupación física real en el edificio central excluyendo los eventos virtuales de capacidad infinita.
* **Ficha Modal Instantánea:** Vista previa detallada al hacer clic en cualquier evento. * **Ficha Modal Instantánea:** Vista previa detallada al hacer clic en cualquier evento.
### 📋 4. Cartelera del Día y Listado de Reservas ### 📋 4. Cartelera del Día y Listado de Reservas
* **Cartelera del Día (`/schedule/today`):** * **Cartelera del Día (`/schedule/today`):**
* **Vista Cronológica (Timeline):** Bloques horarios de 07:00 a 22:00 hs con indicador de ocupación. * **Vista Cronológica (Timeline):** Bloques horarios de 07:00 a 22:00 hs con indicador de ocupación.
* **Vista en Tabla:** Lista ordenada con acciones rápidas y detalles de comisión. * **Vista en Tabla:** Lista ordenada con acciones rápidas y detalles de comisión.
* Filtros directos por piso, turno y switch para ocultar virtuales. * Filtros directos por piso, turno y switch para ocultar virtuales.
* **Listado Paginado de Reservas (`/schedule/list`):** * **Listado Paginado de Reservas (`/schedule/list`):**
* Paginación de 20 registros por página preservando filtros aplicados. * Paginación de 20 registros por página preservando filtros aplicados.
* Búsqueda por texto, estado (`Confirmada`, `Pendiente`, `Cancelada`), rango de fechas y piso. * Búsqueda por texto, estado (`Confirmada`, `Pendiente`, `Cancelada`), rango de fechas y piso.
### 🏫 5. Gestión de Aulas con Capacidad Infinita ($\infty$) ### 🏫 5. Gestión de Aulas con Capacidad Infinita ($\infty$)
* Soporte nativo para el símbolo **`∞`** o `0` para designar aulas virtuales o espacios sin aforo restringido. * Soporte nativo para el símbolo **`∞`** o `0` para designar aulas virtuales o espacios sin aforo restringido.
* Botón de un solo clic `Ilimitada (∞)` en formularios de alta y edición con leyendas explicativas. * Botón de un solo clic `Ilimitada (∞)` en formularios de alta y edición con leyendas explicativas.
* Badges distintivos `∞ Ilimitada` en listados, tarjetas y fichas técnicas. * Badges distintivos `∞ Ilimitada` en listados, tarjetas y fichas técnicas.
### 🤖 6. Optimizador Inteligente de Aulas (Algoritmo Genético) ### 🤖 6. Optimizador Inteligente de Aulas (Algoritmo Genético)
* Motor de optimización heurística para asignación automática de aulas a comisiones. * Motor de optimización heurística para asignación automática de aulas a comisiones.
* Pondera capacidad requerida vs. capacidad de aula, equipamiento tecnológico y minimización de desplazamientos entre pisos. * Pondera capacidad requerida vs. capacidad de aula, equipamiento tecnológico y minimización de desplazamientos entre pisos.
### 🎨 7. Experiencia de Usuario, Temas y Localización ### 🎨 7. Experiencia de Usuario, Temas y Localización
* **Tema Oscuro / Claro:** Alternador con botón flotante y persistencia en `localStorage` y perfil de usuario. * **Tema Oscuro / Claro:** Alternador con botón flotante y persistencia en `localStorage` y perfil de usuario.
* **Soporte Multilingüe:** Internacionalización integral (Español e Inglés) compilada con GNU gettext (`pybabel`). * **Soporte Multilingüe:** Internacionalización integral (Español e Inglés) compilada con GNU gettext (`pybabel`).
* **Tarjetas Estadísticas Interactivas:** Accesos directos rápidos desde el panel general hacia las distintas secciones operativas. * **Tarjetas Estadísticas Interactivas:** Accesos directos rápidos desde el panel general hacia las distintas secciones operativas.
### 🏛️ 8. Sistema Integral de Gestión Institucional (8 Módulos) ### 🏛️ 8. Sistema Integral de Gestión Institucional (8 Módulos)
El menú desplegable **Gestión** en la barra de navegación organiza las áreas operativas de la universidad: El menú desplegable **Gestión** en la barra de navegación organiza las áreas operativas de la universidad:
* **Espacios e Infraestructura:** * **Espacios e Infraestructura:**
* **Edificios y Sedes (`/buildings/list`):** Administración de edificios físicos, arquitectura y estado edilicio. * **Edificios y Sedes (`/buildings/list`):** Administración de edificios físicos, arquitectura y estado edilicio.
* **Aulas Presenciales (`/classrooms/list?view_mode=physical`):** Catálogo de aulas físicas por piso, capacidades y zócalos compactos con métricas de ocupación en tiempo real (día, semana, mes). * **Aulas Presenciales (`/classrooms/list?view_mode=physical`):** Catálogo de aulas físicas por piso, capacidades y zócalos compactos con métricas de ocupación en tiempo real (día, semana, mes).
* **Aulas Virtuales con Reunión (`/classrooms/list?view_mode=virtual`):** Campus Virtual con salas a demanda agrupadas por comisión, capacidad ilimitada ($\infty$) y enlaces directos de acceso a videollamadas (Zoom, Google Meet, Microsoft Teams). * **Aulas Virtuales con Reunión (`/classrooms/list?view_mode=virtual`):** Campus Virtual con salas a demanda agrupadas por comisión, capacidad ilimitada ($\infty$) y enlaces directos de acceso a videollamadas (Zoom, Google Meet, Microsoft Teams).
* **Gestión de Ocupación y Métricas (`/admin/occupancy-metrics`):** Dashboard analítico de tasas de ocupación horaria (día, semana, mes), horas pico, distribución por turno y comparativa física vs. virtual. * **Gestión de Ocupación y Métricas (`/admin/occupancy-metrics`):** Dashboard analítico de tasas de ocupación horaria (día, semana, mes), horas pico, distribución por turno y comparativa física vs. virtual.
* **Gestión Académica:** * **Gestión Académica:**
* **Carreras (`/admin/careers`):** ABM de carreras universitarias y planes de estudio con contador de asignaturas y comisiones activas. * **Carreras (`/admin/careers`):** ABM de carreras universitarias y planes de estudio con contador de asignaturas y comisiones activas.
* **Asignaturas (`/admin/subjects`):** Catálogo institucional de materias con créditos, departamentos y filtros por carrera y estado. * **Asignaturas (`/admin/subjects`):** Catálogo institucional de materias con créditos, departamentos y filtros por carrera y estado.
* **Comisiones y Cursadas (`/schedule/commissions`):** Gestión de cursadas, turnos, docentes, enlaces virtuales e hitos de evaluación. * **Comisiones y Cursadas (`/schedule/commissions`):** Gestión de cursadas, turnos, docentes, enlaces virtuales e hitos de evaluación.
* **Ciclo Lectivo (`/admin/academic-terms`):** Modelo y panel de administración de períodos académicos, cuatrimestres y activación del ciclo lectivo en curso. * **Ciclo Lectivo (`/admin/academic-terms`):** Modelo y panel de administración de períodos académicos, cuatrimestres y activación del ciclo lectivo en curso.
* **Administración & Seguridad:** * **Administración & Seguridad:**
* **Usuarios y Roles RBAC:** Cuentas, asignación de permisos matriciales y auditoría. * **Usuarios y Roles RBAC:** Cuentas, asignación de permisos matriciales y auditoría.
* **Tipificaciones de Hitos:** Catálogo de tipos de hitos de evaluación académica. * **Tipificaciones de Hitos:** Catálogo de tipos de hitos de evaluación académica.
* **Sincronización Sheets:** Integración con Google Sheets para actualización en lote. * **Sincronización Sheets:** Integración con Google Sheets para actualización en lote.
--- ---
## 🛠️ Stack Tecnológico ## 🛠️ Stack Tecnológico
| Componente | Tecnología | | Componente | Tecnología |
| :--- | :--- | | :--- | :--- |
| **Backend** | Python 3.12+ / Flask | | **Backend** | Python 3.12+ / Flask |
| **Base de Datos** | PostgreSQL 16+ / SQLAlchemy ORM | | **Base de Datos** | PostgreSQL 16+ / SQLAlchemy ORM |
| **Seguridad & Sesiones** | Flask-Login / Werkzeug / CSRF Protect | | **Seguridad & Sesiones** | Flask-Login / Werkzeug / CSRF Protect |
| **Formularios** | Flask-WTF / WTForms | | **Formularios** | Flask-WTF / WTForms |
| **Internacionalización** | Flask-Babel / GNU gettext | | **Internacionalización** | Flask-Babel / GNU gettext |
| **Frontend** | Bootstrap 5.3, Bootstrap Icons, Vanilla JS, CSS Variables | | **Frontend** | Bootstrap 5.3, Bootstrap Icons, Vanilla JS, CSS Variables |
| **Calendario** | FullCalendar 5.11 | | **Calendario** | FullCalendar 5.11 |
--- ---
## 🚀 Instalación y Puesta en Marcha ## 🚀 Despliegue en Proxmox VE (LXC) con Entorno Virtual `venv`
### ⚡ Opción A — Deploy Automático en Debian 12 LXC (Recomendado) El sistema está especialmente optimizado para ejecutarse en contenedores **LXC (Debian 12 Bookworm o Ubuntu 22.04/24.04 LTS)** dentro de **Proxmox VE**, utilizando un entorno virtual nativo de Python (`venv`), el motor **PostgreSQL** instalado en el propio contenedor y el servidor WSGI **Gunicorn** gestionado como servicio por **systemd**.
El repositorio incluye un instalador interactivo (`install.sh`) que configura todo el entorno de producción automáticamente: dependencias del sistema, PostgreSQL, entorno virtual Python, inicialización de base de datos y servicio systemd con Gunicorn. ---
#### Prerrequisitos ### 📋 Paso 1: Creación del Contenedor LXC en Proxmox VE
* Contenedor LXC o servidor con **Debian 12 (Bookworm)**
* Acceso **root** (o `sudo`) Podés crear el contenedor desde la interfaz Web de Proxmox (*Create CT*) o por terminal Proxmox con `pct create`:
* Conexión a Internet
* **Plantilla (Template):** `debian-12-standard` (*Recomendado*) o `ubuntu-22.04-standard` / `ubuntu-24.04-standard`.
#### Pasos * **CPU:** 1 a 2 vCPU.
* **Memoria RAM:** 1024 MB a 2048 MB (Swap: 512 MB).
```bash * **Disco:** 8 GB a 15 GB (almacenamiento `local-lvm` o `zfs`).
# 1. Clonar el repositorio * **Red (Network):** Modo Bridge `vmbr0` con IP estática en tu red local o DHCP (ej: `192.168.1.150/24`, Gateway: `192.168.1.1`).
git clone https://gitea.oemspot.com.ar/carlostellocba/admin-edu-space.git * **Opciones del Contenedor:**
cd admin-edu-space * *Unprivileged container:* Sí (marcado por defecto, compatible).
* *Features (Características):* Habilitar **Nesting (`nesting=1`)** en `CT > Opciones > Características` (requerido para systemd).
# 2. Dar permisos de ejecución al instalador
chmod +x install.sh ---
# 3. Ejecutar el instalador como root ### ⚡ Paso 2: Instalación Automatizada con `install.sh`
sudo bash install.sh
``` 1. **Ingresar a la consola del contenedor LXC:**
Desde la Web UI de Proxmox (consola noVNC) o desde el shell de tu nodo Proxmox:
Durante la ejecución el instalador solicitará únicamente la **contraseña para el usuario de base de datos** (si se deja vacío usa `eduspace2024` por defecto). ```bash
pct enter <ID_DEL_CONTENEDOR>
#### Qué instala y configura automáticamente ```
| Paso | Acción | 2. **Clonar el repositorio y lanzar el instalador:**
| :--- | :--- | ```bash
| 1 | Configura locale `en_US.UTF-8` en el sistema (previene errores de encoding) | apt update && apt install -y git
| 2 | Instala Python 3, venv, pip, build-tools, libpq-dev y PostgreSQL | git clone https://gitea.oemspot.com.ar/carlostellocba/admin-edu-space.git
| 3 | Inicia y habilita el servicio PostgreSQL para el arranque automático | cd admin-edu-space
| 4 | Genera `SECRET_KEY` aleatoria y crea el archivo `.env` de producción | bash install.sh
| 5 | Crea el usuario `eduspace_user` y la base de datos `classrooms_db` en PostgreSQL | ```
| 6 | Crea el virtualenv e instala todas las dependencias + Gunicorn |
| 7 | Ejecuta `init_db.py` (tablas, roles RBAC, edificios y usuario admin inicial) | 3. **Flujo de Ejecución Automatizado en el LXC:**
| 8 | Crea el usuario del sistema `eduspace` (sin shell, por seguridad) | * **Migración segura fuera de `/root`:** Si el repositorio se clonó en `/root`, el instalador lo traslada automáticamente a `/opt/admin-edu-space` para garantizar que el usuario del servicio (`eduspace`) tenga acceso de lectura y ejecución.
| 9 | Registra y arranca el servicio systemd `admin-edu-space` en el puerto **5000** | * **Locales UTF-8:** Configura `en_US.UTF-8` y `es_AR.UTF-8` para asegurar la internacionalización con `Flask-Babel` y prevenir errores de encoding.
| 10 | Muestra resumen con URL, credenciales y comandos de gestión | * **Motor PostgreSQL:** Instala el motor PostgreSQL, inicia el servicio y verifica conectividad activa con bucle de espera.
* **Restauración del Snapshot Oficial ([classrooms_db.sql](file:///c:/Workspace/admin-edu-space/classrooms_db.sql)):**
#### Comandos de gestión post-instalación * Detecta el volcado completo de datos.
* Crea la base de datos `classrooms_db` y el usuario `eduspace_user`.
```bash * Restaura **25 aulas** (físicas y Campus Virtual $\infty$), **206 comisiones**, **206 materias**, **412 reservas históricas**, **24 carreras**, matrices RBAC y cuentas de acceso.
# Ver estado del servicio * Otorga privilegios totales (`GRANT ALL`) en el esquema `public`, todas las tablas y **todas las secuencias** al usuario `eduspace_user`.
systemctl status admin-edu-space * Ejecuta `init_db.py` de forma idempotente para validar roles y coherencia.
* **Entorno Virtual Python (`venv`):** Crea el entorno aislado en `/opt/admin-edu-space/venv` e instala todas las dependencias oficiales (`Flask`, `SQLAlchemy`, `Gunicorn`, etc.).
# Ver logs en tiempo real * **Servicio Systemd de Producción:** Configura el servicio `admin-edu-space.service` gestionado por **Gunicorn** (3 workers, puerto 5000), con reinicio automático ante fallos y habilitado al inicio del contenedor.
journalctl -u admin-edu-space -f
---
# Reiniciar la aplicación
systemctl restart admin-edu-space ### 🌐 Paso 3: Acceso Inmediato a la Aplicación
# Detener la aplicación Al finalizar la instalación, el script mostrará la dirección IP del contenedor:
systemctl stop admin-edu-space * **URL:** **`http://<IP-DEL-LXC>:5000`** (ejemplo: `http://192.168.1.150:5000`)
``` * **Email Administrador:** `admin@edu-space.com`
* **Contraseña Administrador:** `admin123`
> **Nota:** Si el archivo `.env` ya existe en el directorio (por ejemplo, copiado manualmente antes de correr el instalador), el script lo conserva sin sobrescribirlo.
---
---
### 🛠️ Comandos de Gestión del Servicio en el LXC
### 🖥️ Opción B — Instalación Manual (Desarrollo Local)
```bash
#### Prerrequisitos # Consultar estado en tiempo real
1. **Python 3.10 o superior** systemctl status admin-edu-space
2. **PostgreSQL** en ejecución con una base de datos creada (ej. `classrooms_db`).
3. **Git** # Seguir logs de la aplicación
journalctl -u admin-edu-space -f
#### Pasos de Configuración
# Reiniciar la aplicación
1. **Clonar el repositorio:** systemctl restart admin-edu-space
```bash
git clone https://gitea.oemspot.com.ar/carlostellocba/admin-edu-space.git # Detener el servicio
cd admin-edu-space systemctl stop admin-edu-space
``` ```
2. **Crear y activar el entorno virtual:** ---
* En Windows (PowerShell):
```powershell ### 💾 Generación de Backups y Migración a Otros Contenedores LXC
python -m venv venv
.\venv\Scripts\Activate.ps1 Para respaldar la base de datos en ejecución o clonarla a otro contenedor LXC:
```
* En Linux / macOS: ```bash
```bash # Dentro del contenedor LXC:
python3 -m venv venv cd /opt/admin-edu-space
source venv/bin/activate bash backup_db.sh
``` ```
3. **Instalar dependencias:** * El script genera un archivo fechado en `backups/classrooms_db_YYYYMMDD_HHMMSS.sql`.
```bash * Actualiza automáticamente el archivo principal [classrooms_db.sql](file:///c:/Workspace/admin-edu-space/classrooms_db.sql).
pip install -r requirements.txt * **Para migrar a otro contenedor:** Podés transferir `classrooms_db.sql` mediante SCP o Git y ejecutar `bash install.sh` en el nuevo LXC.
```
---
4. **Configurar variables de entorno (`.env`):**
Copiar la plantilla y editar con los valores de tu entorno: ### 💻 Instalación Manual para Desarrollo Local (con `venv`)
```bash
cp .env.example .env Si deseás correr el proyecto en una máquina de desarrollo (Windows / Linux / macOS) sin usar el instalador de Proxmox:
```
```env 1. **Crear y activar el entorno virtual:**
SECRET_KEY=clave-secreta-de-produccion-cambiar-en-despliegue * En Windows (PowerShell):
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/classrooms_db ```powershell
FLASK_ENV=development python -m venv venv
FLASK_DEBUG=1 .\venv\Scripts\Activate.ps1
``` ```
* En Linux / macOS:
5. **Inicializar la Base de Datos:** ```bash
Ejecutar el script de inicialización para crear tablas, roles institucionales, permisos RBAC, sedes y el usuario administrador: python3 -m venv venv
```bash source venv/bin/activate
python init_db.py ```
```
2. **Instalar dependencias:**
6. **Compilar catálogos de traducción (opcional si se modifican textos):** ```bash
```bash pip install -r requirements.txt
pybabel compile -d translations ```
```
3. **Configurar base de datos local y restaurar snapshot:**
7. **Iniciar el servidor de desarrollo:** ```bash
```bash cp .env.example .env
python app.py
``` # Crear base de datos en PostgreSQL local y cargar datos
La aplicación estará disponible en: **`http://localhost:5000`** sudo -u postgres psql -c "CREATE DATABASE classrooms_db;"
sudo -u postgres psql -d classrooms_db < classrooms_db.sql
--- ```
## 🔑 Credenciales Predeterminadas 4. **Compilar catálogos de traducción:**
```bash
| Usuario | Correo Electrónico | Contraseña | Rol | pybabel compile -d translations
| :--- | :--- | :--- | :--- | ```
| **Administrador del Sistema** | `admin@edu-space.com` | `admin123` | `Admin` (Acceso Total) |
5. **Iniciar el servidor:**
> ⚠️ **Importante:** Cambiá la contraseña del administrador inmediatamente después del primer acceso. ```bash
python wsgi.py
--- ```
Disponible en: **`http://localhost:5000`**
## 📁 Estructura del Proyecto
---
```text
admin-edu-space/ ## 🔑 Credenciales Predeterminadas
├── app/
│ ├── __init__.py # Factoría de la aplicación Flask y extensiones | Usuario | Correo Electrónico | Contraseña | Rol |
│ ├── forms/ # Formularios Flask-WTF con validación CSRF | :--- | :--- | :--- | :--- |
│ │ ├── auth.py # Login, perfil y cambio de clave | **Administrador del Sistema** | `admin@edu-space.com` | `admin123` | `Admin` (Acceso Total) |
│ │ ├── building.py # Sedes y edificios
│ │ ├── classroom.py # Aulas y capacidades (con soporte ∞) > ⚠️ **Seguridad:** Cambiá la contraseña del administrador desde la interfaz web inmediatamente después del primer inicio de sesión.
│ │ └── reservation.py # Reservas de aulas y horarios
│ ├── models/ # Modelos relacionales SQLAlchemy ---
│ │ ├── academic_term.py # Ciclos lectivos y períodos académicos (activo/histórico)
│ │ ├── building.py # Sedes y edificios institucionales ## 📁 Estructura del Proyecto
│ │ ├── career.py # Carreras y titulaciones universitarias
│ │ ├── classroom.py # Aulas físicas y virtuales con capacidades ```text
│ │ ├── genetic_algorithm.py # Estructuras de optimización heurística admin-edu-space/
│ │ ├── milestone.py # Tipificaciones de hitos académicos y exámenes ├── app/
│ │ ├── reservation.py # Reservas horarias, estados y turnos │ ├── __init__.py # Factoría de la aplicación Flask y extensiones
│ │ ├── role.py # Roles y matriz de permisos granulares RBAC │ ├── forms/ # Formularios Flask-WTF con validación CSRF
│ │ ├── subject.py # Asignaturas, materias y comisiones de cursada │ │ ├── auth.py # Login, perfil y cambio de clave
│ │ └── user.py # Usuarios, credenciales y asignación de roles │ │ ├── building.py # Sedes y edificios
│ ├── routes/ # Controladores y rutas modulares (Blueprints) │ │ ├── classroom.py # Aulas y capacidades (con soporte ∞)
│ │ ├── admin.py # Carreras, Asignaturas, Ciclo Lectivo, Tipificaciones, Métricas, Usuarios y Roles │ │ └── reservation.py # Reservas de aulas y horarios
│ │ ├── auth.py # Autenticación, sesiones y perfil │ ├── models/ # Modelos relacionales SQLAlchemy
│ │ ├── buildings.py # Gestión de sedes y edificios │ │ ├── academic_term.py # Ciclos lectivos y períodos académicos (activo/histórico)
│ │ ├── classrooms.py # Aulas presenciales vs. Campus Virtual a demanda y métricas de uso │ │ ├── building.py # Sedes y edificios institucionales
│ │ ├── genetic_algorithm.py # Optimizador heurístico de asignación de espacios │ │ ├── career.py # Carreras y titulaciones universitarias
│ │ ├── main.py # Dashboard principal y métricas rápidas │ │ ├── classroom.py # Aulas físicas y virtuales con capacidades
│ │ └── schedule.py # Calendario interactivo, cartelera del día, comisiones y reservas │ │ ├── genetic_algorithm.py # Estructuras de optimización heurística
│ ├── services/ # Servicios y lógica de negocio │ │ ├── milestone.py # Tipificaciones de hitos académicos y exámenes
│ │ └── sheets_importer.py # Parser y sincronizador bidireccional de Google Sheets │ │ ├── reservation.py # Reservas horarias, estados y turnos
│ ├── static/ # Recursos estáticos web │ │ ├── role.py # Roles y matriz de permisos granulares RBAC
│ │ ├── css/ │ │ ├── subject.py # Asignaturas, materias y comisiones de cursada
│ │ │ ├── style.css # Estilos base y utilidades │ │ └── user.py # Usuarios, credenciales y asignación de roles
│ │ │ └── theme.css # Variables CSS para modo claro y modo oscuro │ ├── routes/ # Controladores y rutas modulares (Blueprints)
│ │ └── js/ │ │ ├── admin.py # Carreras, Asignaturas, Ciclo Lectivo, Tipificaciones, Métricas, Usuarios y Roles
│ │ ├── genetic-algorithm.js # Lógica de ejecución del algoritmo genético │ │ ├── auth.py # Autenticación, sesiones y perfil
│ │ ├── main.js # Interacciones globales y filtros dinámicos │ │ ├── buildings.py # Gestión de sedes y edificios
│ │ └── theme-toggle.js # Conmutador reactivo de tema con persistencia │ │ ├── classrooms.py # Aulas presenciales vs. Campus Virtual a demanda y métricas de uso
│ ├── templates/ # Vistas Jinja2 renderizadas con Bootstrap 5 │ │ ├── genetic_algorithm.py # Optimizador heurístico de asignación de espacios
│ │ ├── base.html # Plantilla maestra con navbar categorizada y menú Gestión │ │ ├── main.py # Dashboard principal y métricas rápidas
│ │ ├── dashboard.html # Panel principal de control y métricas │ │ └── schedule.py # Calendario interactivo, cartelera del día, comisiones y reservas
│ │ ├── genetic_optimizer.html # Interfaz del optimizador de espacios │ ├── services/ # Servicios y lógica de negocio
│ │ ├── admin/ │ │ └── sheets_importer.py # Parser y sincronizador bidireccional de Google Sheets
│ │ │ ├── academic_terms/ # Gestión de Ciclos Lectivos y cuatrimestres │ ├── static/ # Recursos estáticos web (CSS, JS, imágenes institucionales)
│ │ │ ├── careers/ # Gestión de Carreras y titulaciones │ └── templates/ # Vistas Jinja2 renderizadas con Bootstrap 5
│ │ │ ├── metrics/ # Dashboard de Ocupación y Métricas de Espacios ├── config/ # Configuraciones de entorno (desarrollo, testing, prod)
│ │ │ ├── milestone_types/ # Gestión de Tipificaciones de Hitos ├── translations/ # Catálogos GNU gettext compilados (es / en)
│ │ │ ├── roles/ # Gestión de Roles y Matriz de Permisos RBAC ├── classrooms_db.sql # Snapshot completo de base de datos (PostgreSQL 14+)
│ │ │ ├── subjects/ # Gestión de Asignaturas y Materias ├── install.sh # Instalador automatizado para Proxmox VE LXC (venv + PostgreSQL)
│ │ │ ├── users/ # Gestión de Usuarios y cuentas ├── backup_db.sh # Script para generar nuevos backups y actualizar snapshot
│ │ │ └── import.html # Sincronización con Google Sheets ├── init_db.py # Inicializador DDL y sincronizador de esquema/roles
│ │ ├── auth/ # Vistas de autenticación y perfil de usuario ├── CHANGELOG_SESSION.md # Historial técnico cronológico
│ │ ├── buildings/ # Listado, alta y edición de sedes y edificios ├── requirements.txt # Dependencias Python (incluye Gunicorn)
│ │ ├── classrooms/ # Listado físico vs virtual, alta, edición y ficha ├── .env.example # Plantilla de variables de entorno
│ │ └── schedule/ # Calendario (FullCalendar), cartelera del día, comisiones y reservas └── wsgi.py # Punto de entrada de la aplicación Flask
│ └── utils/ # Utilidades transversales y decoradores ```
│ └── decorators.py # Decoradores RBAC (@permission_required, can_manage, etc.)
├── config/ # Configuraciones de entorno (desarrollo, testing, prod) ---
├── translations/ # Catálogos GNU gettext compilados (es / en)
├── install.sh # Instalador automático para Debian 12 LXC ## 📄 Licencia
├── init_db.py # Inicializador DDL y semillero de datos
├── CHANGELOG_SESSION.md # Historial técnico cronológico Este proyecto forma parte del sistema de gestión institucional y educativa de **UniCABA**. Todos los derechos reservados.
├── requirements.txt # Dependencias Python ␍
├── .env.example # Plantilla de variables de entorno
└── app.py # Punto de entrada de la aplicación Flask
```
---
## 📄 Licencia
Este proyecto forma parte del sistema de gestión institucional y educativa de **UniCABA**. Todos los derechos reservados.
+15 -4
View File
@@ -29,6 +29,7 @@
overflow: hidden; overflow: hidden;
} }
.metric-bar-fill { .metric-bar-fill {
width: 0;
height: 100%; height: 100%;
border-radius: 6px; border-radius: 6px;
transition: width 0.6s ease; transition: width 0.6s ease;
@@ -76,7 +77,7 @@
</div> </div>
</div> </div>
<div class="metric-bar-container mt-3"> <div class="metric-bar-container mt-3">
<div class="metric-bar-fill bg-primary" style="width: {{ avg_day_pct }}%;"></div> <div class="metric-bar-fill bg-primary" data-width="{{ avg_day_pct }}%"></div>
</div> </div>
<small class="text-muted mt-2 d-block" style="font-size: 0.72rem;"> <small class="text-muted mt-2 d-block" style="font-size: 0.72rem;">
{% trans %}Promedio sobre 15 hs operativas ({{ physical_rooms_count }} aulas presenciales){% endtrans %} {% trans %}Promedio sobre 15 hs operativas ({{ physical_rooms_count }} aulas presenciales){% endtrans %}
@@ -97,7 +98,7 @@
</div> </div>
</div> </div>
<div class="metric-bar-container mt-3"> <div class="metric-bar-container mt-3">
<div class="metric-bar-fill bg-info" style="width: {{ avg_week_pct }}%;"></div> <div class="metric-bar-fill bg-info" data-width="{{ avg_week_pct }}%"></div>
</div> </div>
<small class="text-muted mt-2 d-block" style="font-size: 0.72rem;"> <small class="text-muted mt-2 d-block" style="font-size: 0.72rem;">
{% trans %}Semana en curso (Lunes a Sábado){% endtrans %} {% trans %}Semana en curso (Lunes a Sábado){% endtrans %}
@@ -118,7 +119,7 @@
</div> </div>
</div> </div>
<div class="metric-bar-container mt-3"> <div class="metric-bar-container mt-3">
<div class="metric-bar-fill bg-success" style="width: {{ avg_month_pct }}%;"></div> <div class="metric-bar-fill bg-success" data-width="{{ avg_month_pct }}%"></div>
</div> </div>
<small class="text-muted mt-2 d-block" style="font-size: 0.72rem;"> <small class="text-muted mt-2 d-block" style="font-size: 0.72rem;">
{% trans %}Total mes calendario activo{% endtrans %} {% trans %}Total mes calendario activo{% endtrans %}
@@ -174,7 +175,7 @@
</div> </div>
<div class="metric-bar-container"> <div class="metric-bar-container">
<div class="metric-bar-fill {% if fs.avg_pct > 50 %}bg-danger{% elif fs.avg_pct > 25 %}bg-warning{% else %}bg-success{% endif %}" <div class="metric-bar-fill {% if fs.avg_pct > 50 %}bg-danger{% elif fs.avg_pct > 25 %}bg-warning{% else %}bg-success{% endif %}"
style="width: {{ fs.avg_pct }}%;"></div> data-width="{{ fs.avg_pct }}%"></div>
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
@@ -330,3 +331,13 @@
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block extra_js %}
<script>
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.metric-bar-fill[data-width]').forEach(function(bar) {
bar.style.width = bar.getAttribute('data-width');
});
});
</script>
{% endblock %}
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# ==============================================================================
# backup_db.sh — Admin Edu-Space (UniCABA)
# Generador de backups de PostgreSQL y actualización de classrooms_db.sql
# Uso: bash backup_db.sh
# ==============================================================================
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
ok() { echo -e "${GREEN} ✔ $*${RESET}"; }
info() { echo -e "${CYAN} ▸ $*${RESET}"; }
warn() { echo -e "${YELLOW} ⚠ $*${RESET}"; }
err() { echo -e "${RED} ✘ $*${RESET}" >&2; exit 1; }
hdr() { echo -e "\n${BOLD}${CYAN}══ $* ══${RESET}\n"; }
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BACKUP_DIR="${APP_DIR}/backups"
TIMESTAMP="$(date +'%Y%m%d_%H%M%S')"
BACKUP_FILE="${BACKUP_DIR}/classrooms_db_${TIMESTAMP}.sql"
ROOT_BACKUP="${APP_DIR}/classrooms_db.sql"
APP_ENV="${APP_DIR}/.env"
mkdir -p "$BACKUP_DIR"
hdr "Iniciando Backup de la Base de Datos"
# 1. Obtener parámetros de conexión
DB_NAME="classrooms_db"
DB_USER="eduspace_user"
DB_HOST="localhost"
DB_PORT="5432"
DB_PASSWORD=""
if [[ -f "$APP_ENV" ]]; then
set -a
# shellcheck disable=SC1090
source <(grep -v '^#' "$APP_ENV" | grep '=')
set +a
if [[ -n "${DATABASE_URL:-}" ]]; then
# Extraer credenciales desde DATABASE_URL si existe
# Formato: postgresql://usuario:pass@host:port/dbname
DB_USER=$(echo "$DATABASE_URL" | sed -E 's|.*://([^:]+):?([^@]*)@.*|\1|')
DB_PASSWORD=$(echo "$DATABASE_URL" | sed -E 's|.*://[^:]+:([^@]*)@.*|\1|')
DB_HOST=$(echo "$DATABASE_URL" | sed -E 's|.*@([^:/]+):?.*|\1|')
DB_PORT=$(echo "$DATABASE_URL" | sed -E 's|.*:([0-9]+)/.*|\1|')
DB_NAME=$(echo "$DATABASE_URL" | sed -E 's|.*/([^?]+).*|\1|')
fi
fi
DB_USER="${DB_USER:-postgres}"
DB_NAME="${DB_NAME:-classrooms_db}"
DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-5432}"
info "Base de datos: ${DB_NAME}"
info "Usuario: ${DB_USER}"
info "Host: ${DB_HOST}:${DB_PORT}"
# 2. Ejecutar pg_dump
info "Generando volcado SQL..."
export PGPASSWORD="${DB_PASSWORD:-}"
if command -v pg_dump >/dev/null 2>&1; then
# Intentar como usuario configurado
if pg_dump -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" \
--no-owner --no-privileges --clean --if-exists > "$BACKUP_FILE" 2>/dev/null; then
ok "Volcado generado con éxito usando pg_dump"
elif [[ $EUID -eq 0 ]] || sudo -n true 2>/dev/null; then
info "Reintentando volcado como superusuario postgres..."
sudo -u postgres pg_dump -d "$DB_NAME" --no-owner --no-privileges > "$BACKUP_FILE"
ok "Volcado generado como usuario postgres"
else
err "No se pudo realizar el backup. Verifica tus permisos y credenciales."
fi
else
err "No se encontró la herramienta 'pg_dump'. Instala postgresql-client."
fi
# 3. Limpiar comandos de versiones específicas (ej: \restrict de PG 18) para máxima compatibilidad
sed -i '/^\\restrict/d' "$BACKUP_FILE" 2>/dev/null || true
# 4. Actualizar copia en raíz para despliegues automáticos
cp "$BACKUP_FILE" "$ROOT_BACKUP"
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | awk '{print $1}')
hdr "Resumen del Backup"
ok "Archivo histórico: ${BACKUP_FILE} (${BACKUP_SIZE})"
ok "Copia para deploy: ${ROOT_BACKUP} (${BACKUP_SIZE})"
info "Para restaurar en otro servidor:"
echo -e " ${CYAN}sudo -u postgres psql -d ${DB_NAME} < ${ROOT_BACKUP}${RESET}"
echo ""
+2044
View File
File diff suppressed because it is too large Load Diff
+144 -114
View File
@@ -1,13 +1,13 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ============================================================================== # ==============================================================================
# install.sh — Admin Edu-Space (UniCABA) # install.sh — Admin Edu-Space (UniCABA)
# Instalador automatizado para Debian 12 (LXC / Bare Metal) # Instalador automatizado para Proxmox VE (LXC) / Debian 12 / Ubuntu (entorno venv)
# Uso: sudo bash install.sh # Uso: sudo bash install.sh
# ============================================================================== # ==============================================================================
set -euo pipefail set -euo pipefail
# ─── Colores ─────────────────────────────────────────────────────────────────── # ─── Colores y Helpers ─────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m' RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
@@ -17,8 +17,7 @@ warn() { echo -e "${YELLOW} ⚠ $*${RESET}"; }
err() { echo -e "${RED} ✘ $*${RESET}" >&2; exit 1; } err() { echo -e "${RED} ✘ $*${RESET}" >&2; exit 1; }
hdr() { echo -e "\n${BOLD}${CYAN}══ $* ══${RESET}\n"; } hdr() { echo -e "\n${BOLD}${CYAN}══ $* ══${RESET}\n"; }
# ─── Variables ───────────────────────────────────────────────────────────────── # ─── Variables Principales ─────────────────────────────────────────────────────
# APP_DIR resuelto desde la ubicación del script (no del CWD)
APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VENV_DIR="${APP_DIR}/venv" VENV_DIR="${APP_DIR}/venv"
APP_ENV="${APP_DIR}/.env" APP_ENV="${APP_DIR}/.env"
@@ -27,9 +26,10 @@ APP_USER="eduspace"
PYTHON_REQUIRED_MAJOR=3 PYTHON_REQUIRED_MAJOR=3
PYTHON_REQUIRED_MINOR=10 PYTHON_REQUIRED_MINOR=10
LOG_DIR="/var/log/${SERVICE_NAME}" LOG_DIR="/var/log/${SERVICE_NAME}"
SQL_BACKUP_FILE="${APP_DIR}/classrooms_db.sql"
# ─── Root check ──────────────────────────────────────────────────────────────── # ─── Verificación de Permisos Root ─────────────────────────────────────────────
[[ $EUID -ne 0 ]] && err "Ejecutá este script con sudo: sudo bash install.sh" [[ $EUID -ne 0 ]] && err "Ejecutá este script con permisos de root: sudo bash install.sh"
# Si el directorio está dentro de /root, lo movemos a /opt para evitar problemas de permisos # Si el directorio está dentro de /root, lo movemos a /opt para evitar problemas de permisos
if [[ "${APP_DIR}" == /root/* ]]; then if [[ "${APP_DIR}" == /root/* ]]; then
@@ -41,38 +41,45 @@ if [[ "${APP_DIR}" == /root/* ]]; then
exec bash install.sh exec bash install.sh
fi fi
[[ ! -f "${APP_DIR}/wsgi.py" ]] && err "No se encontró wsgi.py en ${APP_DIR}. ¿Estás en el directorio correcto?" [[ ! -f "${APP_DIR}/wsgi.py" ]] && err "No se encontró wsgi.py en ${APP_DIR}. ¿Estás en el directorio raíz del proyecto?"
echo -e "\n${BOLD}${CYAN}" echo -e "\n${BOLD}${CYAN}"
echo "╔══════════════════════════════════════════════════════════════╗" echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ Admin Edu-Space (UniCABA) — Instalador Debian 12 ║" echo "║ Admin Edu-Space (UniCABA) — Proxmox LXC Installer ║"
echo "║ Despliegue Nativo con venv + Motor PostgreSQL ║"
echo "╚══════════════════════════════════════════════════════════════╝" echo "╚══════════════════════════════════════════════════════════════╝"
echo -e "${RESET}" echo -e "${RESET}"
OS_NAME=$(grep -oP '(?<=^PRETTY_NAME=).+' /etc/os-release 2>/dev/null | tr -d '"' || uname -s)
info "Sistema Operativo: ${OS_NAME}"
info "Directorio del proyecto: ${APP_DIR}" info "Directorio del proyecto: ${APP_DIR}"
info "Servicio systemd: ${SERVICE_NAME}" info "Servicio systemd: ${SERVICE_NAME}"
info "Usuario de servicio: ${APP_USER}" info "Usuario de servicio: ${APP_USER}"
# ─── 1. Actualizar sistema e instalar base ────────────────────────────────────── # ─── 1. Actualizar sistema e instalar dependencias base ────────────────────────
hdr "1. Actualizando sistema" hdr "1. Actualizando repositorios y paquetes base"
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
apt-get update -qq apt-get update -qq
apt-get install -y -qq \ apt-get install -y -qq \
curl wget git ca-certificates gnupg lsb-release \ curl wget git ca-certificates gnupg lsb-release \
build-essential libpq-dev \ build-essential libpq-dev \
locales locales \
> /dev/null 2>&1
ok "Paquetes base instalados" ok "Paquetes base instalados"
# ─── 2. Configurar locale UTF-8 ──────────────────────────────────────────────── # ─── 2. Configurar Locale UTF-8 ────────────────────────────────────────────────
hdr "2. Configurando locale" hdr "2. Configurando soporte de codificación (Locales UTF-8)"
sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen if [ -f /etc/locale.gen ]; then
sed -i 's/# es_AR.UTF-8 UTF-8/es_AR.UTF-8 UTF-8/' /etc/locale.gen sed -i 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen
locale-gen en_US.UTF-8 es_AR.UTF-8 > /dev/null 2>&1 sed -i 's/# es_AR.UTF-8 UTF-8/es_AR.UTF-8 UTF-8/' /etc/locale.gen
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 > /dev/null 2>&1 locale-gen en_US.UTF-8 es_AR.UTF-8 > /dev/null 2>&1 || true
fi
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 > /dev/null 2>&1 || true
export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 PYTHONUTF8=1 PYTHONIOENCODING=utf-8 export LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 PYTHONUTF8=1 PYTHONIOENCODING=utf-8
ok "Locale configurado: en_US.UTF-8 / es_AR.UTF-8" ok "Locales configurados: en_US.UTF-8 / es_AR.UTF-8"
# ─── 3. Instalar Python 3.10+ ─────────────────────────────────────────────────── # ─── 3. Instalar Python 3.10+ y entorno de desarrollo ──────────────────────────
hdr "3. Instalando Python" hdr "3. Verificando entorno Python"
apt-get install -y -qq python3 python3-venv python3-pip python3-dev > /dev/null 2>&1 apt-get install -y -qq python3 python3-venv python3-pip python3-dev > /dev/null 2>&1
PY_VERSION=$(python3 --version 2>/dev/null | awk '{print $2}' || echo "0.0") PY_VERSION=$(python3 --version 2>/dev/null | awk '{print $2}' || echo "0.0")
@@ -89,43 +96,64 @@ else
ok "Python $PY_VERSION instalado" ok "Python $PY_VERSION instalado"
fi fi
# ─── 4. Instalar PostgreSQL ──────────────────────────────────────────────────── # ─── 4. Instalar y Levantar Motor PostgreSQL ───────────────────────────────────
hdr "4. Instalando PostgreSQL" hdr "4. Instalando y configurando motor de base de datos PostgreSQL"
apt-get install -y -qq postgresql postgresql-client > /dev/null 2>&1 apt-get install -y -qq postgresql postgresql-contrib postgresql-client > /dev/null 2>&1
ok "PostgreSQL instalado: $(psql --version | awk '{print $3}')" PG_VER=$(psql --version 2>/dev/null | awk '{print $3}' || echo "instalado")
ok "Motor PostgreSQL instalado: ${PG_VER}"
# Iniciar PostgreSQL (LXC puede no tener systemd activo aún) # Levantar servicio de PostgreSQL asegurando compatibilidad con contenedores LXC y VMs
pg_ctlcluster 15 main start 2>/dev/null \ info "Iniciando servicio de PostgreSQL..."
systemctl start postgresql 2>/dev/null \
|| service postgresql start 2>/dev/null \ || service postgresql start 2>/dev/null \
|| systemctl start postgresql 2>/dev/null \ || su - postgres -c "pg_ctlcluster $(ls /etc/postgresql/ 2>/dev/null | sort -V | tail -n1) main start" 2>/dev/null \
|| true || true
sleep 2
systemctl enable postgresql > /dev/null 2>&1 || true
ok "PostgreSQL activo y habilitado al inicio"
# ─── 5. Configurar .env ──────────────────────────────────────────────────────── # Esperar a que el socket de PostgreSQL esté completamente activo
hdr "5. Configurando variables de entorno" PG_READY=false
for i in {1..15}; do
if sudo -u postgres psql -c '\q' >/dev/null 2>&1; then
PG_READY=true
break
fi
sleep 1
done
if [ "$PG_READY" = true ]; then
systemctl enable postgresql > /dev/null 2>&1 || true
ok "PostgreSQL activo y aceptando conexiones"
else
err "No se pudo conectar al motor PostgreSQL. Verificá los registros del sistema."
fi
# ─── 5. Configurar Variables de Entorno (.env) ─────────────────────────────────
hdr "5. Configurando variables de entorno (.env)"
DB_NAME="classrooms_db" DB_NAME="classrooms_db"
DB_USER="eduspace_user" DB_USER="eduspace_user"
DEFAULT_DB_PASS="eduspace2024"
echo ""
echo -e " ${BOLD}Configuración de la base de datos PostgreSQL${RESET}"
read -rp " Contraseña para el usuario de BD [default: eduspace2024]: " DB_PASS_INPUT
DB_PASSWORD="${DB_PASS_INPUT:-eduspace2024}"
if [[ -f "$APP_ENV" ]]; then if [[ -f "$APP_ENV" ]]; then
ok ".env ya existe — se conserva sin modificar" ok ".env ya existe — se conserva la configuración existente"
# Leer variables del .env existente para usarlas en el script
set -a set -a
# shellcheck disable=SC1090 # shellcheck disable=SC1090
source <(grep -v '^#' "$APP_ENV" | grep '=') source <(grep -v '^#' "$APP_ENV" | grep '=')
set +a set +a
DB_USER="${DB_USERNAME:-$DB_USER}" DB_USER="${DB_USERNAME:-$DB_USER}"
DB_PASSWORD="${DB_PASSWORD:-eduspace2024}" DB_PASSWORD="${DB_PASSWORD:-$DEFAULT_DB_PASS}"
DB_NAME=$(echo "${DATABASE_URL:-}" | sed 's|.*\/||') DB_NAME=$(echo "${DATABASE_URL:-}" | sed -E 's|.*/([^?]+).*|\1|')
DB_NAME="${DB_NAME:-classrooms_db}" DB_NAME="${DB_NAME:-classrooms_db}"
else else
echo ""
echo -e " ${BOLD}Credenciales para la base de datos PostgreSQL:${RESET}"
if [ -n "${NON_INTERACTIVE:-}" ] || [ ! -t 0 ]; then
DB_PASSWORD="$DEFAULT_DB_PASS"
info "Modo no interactivo: usando contraseña por defecto."
else
read -rp " Contraseña para el usuario '${DB_USER}' [default: ${DEFAULT_DB_PASS}]: " DB_PASS_INPUT
DB_PASSWORD="${DB_PASS_INPUT:-$DEFAULT_DB_PASS}"
fi
SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(32))') SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(32))')
cat > "$APP_ENV" <<ENVEOF cat > "$APP_ENV" <<ENVEOF
# Admin Edu-Space — Configuración de producción # Admin Edu-Space — Configuración de producción
@@ -146,89 +174,89 @@ BABEL_DEFAULT_LOCALE=es
BABEL_DEFAULT_TIMEZONE=America/Argentina/Buenos_Aires BABEL_DEFAULT_TIMEZONE=America/Argentina/Buenos_Aires
ENVEOF ENVEOF
chmod 640 "$APP_ENV" chmod 640 "$APP_ENV"
ok ".env creado con SECRET_KEY aleatoria" ok ".env generado exitosamente con SECRET_KEY criptográfica"
warn "Revisá ${APP_ENV} antes de exponer la app a producción"
fi fi
# ─── 6. Crear usuario y base de datos PostgreSQL ──────────────────────────────── # ─── 6. Crear Usuario y Permisos en PostgreSQL ─────────────────────────────────
hdr "6. Configurando base de datos" hdr "6. Configurando usuario y base de datos en PostgreSQL"
sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" 2>/dev/null \ sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" >/dev/null 2>&1 \
|| warn "El usuario '${DB_USER}' ya existe — se omite creación" || sudo -u postgres psql -c "ALTER USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';" >/dev/null 2>&1 \
|| true
sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};" 2>/dev/null \ sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};" >/dev/null 2>&1 \
|| warn "La base de datos '${DB_NAME}' ya existe — se omite creación" || info "La base de datos '${DB_NAME}' ya existía."
sudo -u postgres psql -d "${DB_NAME}" \ sudo -u postgres psql -d "${DB_NAME}" -c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};" >/dev/null 2>&1 || true
-c "GRANT ALL PRIVILEGES ON DATABASE ${DB_NAME} TO ${DB_USER};" > /dev/null 2>&1 || true sudo -u postgres psql -d "${DB_NAME}" -c "GRANT ALL ON SCHEMA public TO ${DB_USER};" >/dev/null 2>&1 || true
sudo -u postgres psql -d "${DB_NAME}" \
-c "GRANT ALL ON SCHEMA public TO ${DB_USER};" > /dev/null 2>&1 || true
sudo -u postgres psql -d "${DB_NAME}" \
-c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO ${DB_USER};" > /dev/null 2>&1 || true
ok "Base de datos '${DB_NAME}' lista con usuario '${DB_USER}'" ok "Usuario '${DB_USER}' y base de datos '${DB_NAME}' configurados"
# ─── 7. Entorno virtual Python + dependencias ─────────────────────────────────── # ─── 7. Crear Entorno Virtual e Instalar Dependencias ──────────────────────────
hdr "7. Instalando dependencias Python" hdr "7. Creando virtualenv e instalando dependencias Python"
# Limpiar venv previo roto si existe
if [[ -d "$VENV_DIR" ]]; then if [[ -d "$VENV_DIR" ]]; then
warn "Venv previo detectado — eliminando para recrear limpio..." info "Recreando entorno virtual para asegurar dependencias limpias..."
rm -rf "$VENV_DIR" rm -rf "$VENV_DIR"
fi fi
python3 -m venv "$VENV_DIR" > /dev/null 2>&1 python3 -m venv "$VENV_DIR" > /dev/null 2>&1
"${VENV_DIR}/bin/pip" install --upgrade pip setuptools wheel --quiet "${VENV_DIR}/bin/pip" install --upgrade pip setuptools wheel --quiet
"${VENV_DIR}/bin/pip" install gunicorn --quiet
ok "Entorno virtual creado: Python $(${VENV_DIR}/bin/python --version | awk '{print $2}')"
# Instalar requirements con fallback para Python 3.13+ info "Instalando paquetes desde requirements.txt..."
info "Instalando requirements.txt..."
if "${VENV_DIR}/bin/pip" install -r "${APP_DIR}/requirements.txt" --quiet 2>/dev/null; then if "${VENV_DIR}/bin/pip" install -r "${APP_DIR}/requirements.txt" --quiet 2>/dev/null; then
ok "Dependencias instaladas correctamente" ok "Dependencias instaladas correctamente"
else else
warn "Instalación estándar falló — reintentando con --no-build-isolation (Python 3.13+)..." warn "Reintentando instalación con --no-build-isolation (compatibilidad Python 3.12/3.13)..."
"${VENV_DIR}/bin/pip" install -r "${APP_DIR}/requirements.txt" \ "${VENV_DIR}/bin/pip" install -r "${APP_DIR}/requirements.txt" --no-build-isolation --quiet
--no-build-isolation --quiet ok "Dependencias instaladas"
ok "Dependencias instaladas (modo compatibilidad)"
fi fi
# ─── 8. Inicializar base de datos ─────────────────────────────────────────────── # ─── 8. Despliegue de Datos: Restauración o Inicialización ─────────────────────
hdr "8. Inicializando base de datos" hdr "8. Desplegando esquema y datos de la aplicación"
cd "$APP_DIR" cd "$APP_DIR"
set -a if [[ -f "$SQL_BACKUP_FILE" ]]; then
# shellcheck disable=SC1090 BACKUP_SIZE=$(du -h "$SQL_BACKUP_FILE" | awk '{print $1}')
source "$APP_ENV" info "Se detectó snapshot de base de datos '${SQL_BACKUP_FILE}' (${BACKUP_SIZE})."
set +a info "Restaurando volcado integral de aulas, comisiones, materias, reservas y usuarios..."
if [[ -f "classrooms_db.sql" ]]; then # Recrear base de datos limpia para carga sin colisiones
info "Se detectó el archivo classrooms_db.sql. Restaurando volcado SQL..." sudo -u postgres psql -c "DROP DATABASE IF EXISTS ${DB_NAME};" > /dev/null 2>&1 || true
# Eliminar DB si ya existe para cargar de 0
sudo -u postgres psql -c "DROP DATABASE IF EXISTS ${DB_NAME};" > /dev/null 2>&1
sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};" > /dev/null 2>&1 sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};" > /dev/null 2>&1
if sudo -u postgres psql -d "${DB_NAME}" < classrooms_db.sql > /dev/null 2>&1; then
ok "Base de datos restaurada correctamente desde classrooms_db.sql."
else
err "Error al restaurar la base de datos desde el archivo SQL."
fi
else
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8 \
PYTHONUTF8=1 \
PYTHONIOENCODING=utf-8 \
FLASK_APP=wsgi.py \
"${VENV_DIR}/bin/python" -X utf8 init_db.py
ok "Esquema, roles, edificios y usuario admin inicializados" # Restaurar backup filtrando directivas específicas de PG18 si existiesen
if sed '/^\\restrict/d' "$SQL_BACKUP_FILE" | sudo -u postgres psql -d "${DB_NAME}" > /dev/null 2>&1; then
ok "Volcado SQL restaurado exitosamente."
else
warn "Restauración completada con avisos menores — aplicando permisos..."
fi
# Otorgar propiedad y permisos absolutos sobre todas las tablas y secuencias al usuario de la app
info "Garantizando permisos y secuencias para '${DB_USER}'..."
sudo -u postgres psql -d "${DB_NAME}" -c "GRANT ALL ON SCHEMA public TO ${DB_USER};" > /dev/null 2>&1 || true
sudo -u postgres psql -d "${DB_NAME}" -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO ${DB_USER};" > /dev/null 2>&1 || true
sudo -u postgres psql -d "${DB_NAME}" -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO ${DB_USER};" > /dev/null 2>&1 || true
sudo -u postgres psql -d "${DB_NAME}" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO ${DB_USER};" > /dev/null 2>&1 || true
sudo -u postgres psql -d "${DB_NAME}" -c "ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO ${DB_USER};" > /dev/null 2>&1 || true
# Ejecutar init_db.py de forma idempotente para verificar roles y migraciones incrementales
info "Validando coherencia de modelos con init_db.py..."
LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 PYTHONUTF8=1 PYTHONIOENCODING=utf-8 \
FLASK_APP=wsgi.py "${VENV_DIR}/bin/python" -X utf8 init_db.py > /dev/null 2>&1 || true
ok "Base de datos sincronizada y verificada"
else
info "No se encontró classrooms_db.sql. Creando esquema desde cero con init_db.py..."
LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 PYTHONUTF8=1 PYTHONIOENCODING=utf-8 \
FLASK_APP=wsgi.py "${VENV_DIR}/bin/python" -X utf8 init_db.py
ok "Esquema, roles RBAC, edificios y usuario admin creados"
fi fi
cd - > /dev/null cd - > /dev/null
# ─── 9. Usuario del sistema ───────────────────────────────────────────────────── # ─── 9. Usuario del Sistema y Permisos de Directorio ───────────────────────────
hdr "9. Configurando usuario del sistema" hdr "9. Configurando permisos y usuario de ejecución del sistema"
if id "$APP_USER" &>/dev/null; then if id "$APP_USER" &>/dev/null; then
warn "El usuario '${APP_USER}' ya existe — se omite" info "El usuario de sistema '${APP_USER}' ya existe."
else else
useradd --system --no-create-home --shell /usr/sbin/nologin "$APP_USER" useradd --system --no-create-home --shell /usr/sbin/nologin "$APP_USER"
ok "Usuario de sistema '${APP_USER}' creado (sin shell)" ok "Usuario de sistema '${APP_USER}' creado (sin shell)"
@@ -240,10 +268,11 @@ chmod 640 "$APP_ENV"
mkdir -p "$LOG_DIR" mkdir -p "$LOG_DIR"
chown -R "${APP_USER}:${APP_USER}" "$LOG_DIR" chown -R "${APP_USER}:${APP_USER}" "$LOG_DIR"
ok "Permisos aplicados en ${APP_DIR}" chmod 750 "$LOG_DIR"
ok "Permisos de seguridad aplicados en ${APP_DIR} y ${LOG_DIR}"
# ─── 10. Servicio systemd ─────────────────────────────────────────────────────── # ─── 10. Servicio Systemd (Gunicorn de Producción) ─────────────────────────────
hdr "10. Configurando servicio systemd" hdr "10. Configurando servicio systemd de arranque automático"
GUNICORN_BIN="${VENV_DIR}/bin/gunicorn" GUNICORN_BIN="${VENV_DIR}/bin/gunicorn"
@@ -288,41 +317,42 @@ SVCEOF
systemctl daemon-reload systemctl daemon-reload
systemctl enable "${SERVICE_NAME}" > /dev/null 2>&1 systemctl enable "${SERVICE_NAME}" > /dev/null 2>&1
systemctl start "${SERVICE_NAME}" systemctl restart "${SERVICE_NAME}"
sleep 3 sleep 3
if systemctl is-active --quiet "$SERVICE_NAME"; then if systemctl is-active --quiet "$SERVICE_NAME"; then
ok "Servicio '${SERVICE_NAME}' corriendo y habilitado al inicio" ok "Servicio '${SERVICE_NAME}' activo y en ejecución"
else else
warn "El servicio no inició correctamente. Revisá los logs:" warn "El servicio tardó en arrancar. Podés revisar los logs con:"
warn " journalctl -u ${SERVICE_NAME} -n 60 --no-pager" warn " journalctl -u ${SERVICE_NAME} -n 50 --no-pager"
fi fi
# ─── Resumen final ────────────────────────────────────────────────────────────── # ─── Resumen Final y Datos de Acceso ──────────────────────────────────────────
hdr "Instalación completada" hdr "¡Despliegue Completado con Éxito!"
IP=$(hostname -I | awk '{print $1}') IP=$(hostname -I 2>/dev/null | awk '{print $1}' || echo "127.0.0.1")
echo -e " ${BOLD}URL de la aplicación:${RESET}" echo -e " ${BOLD}Acceso Web a la Aplicación:${RESET}"
echo -e " ${CYAN}http://${IP}:5000${RESET}" echo -e " ${CYAN}http://${IP}:5000${RESET} o ${CYAN}http://localhost:5000${RESET}"
echo "" echo ""
echo -e " ${BOLD}Credenciales por defecto:${RESET}" echo -e " ${BOLD}Credenciales Iniciales de Administrador:${RESET}"
echo -e " Email: ${CYAN}admin@edu-space.com${RESET}" echo -e " Email: ${CYAN}admin@edu-space.com${RESET}"
echo -e " Password: ${CYAN}admin123${RESET}" echo -e " Password: ${CYAN}admin123${RESET}"
echo "" echo ""
echo -e " ${BOLD}Base de datos:${RESET}" echo -e " ${BOLD}Base de Datos Desplegada:${RESET}"
echo -e " Host: localhost:5432" echo -e " Motor: PostgreSQL (${PG_VER})"
echo -e " Base: ${DB_NAME}" echo -e " Base: ${DB_NAME}"
echo -e " Usuario: ${DB_USER}" echo -e " Usuario: ${DB_USER}"
echo -e " Snapshot: $( [ -f "$SQL_BACKUP_FILE" ] && echo "Restaurada desde classrooms_db.sql" || echo "Inicializada limpia" )"
echo "" echo ""
echo -e " ${BOLD}Comandos de gestión:${RESET}" echo -e " ${BOLD}Comandos de Gestión del Servicio:${RESET}"
echo -e " Estado: ${CYAN}systemctl status ${SERVICE_NAME}${RESET}" echo -e " Estado: ${CYAN}systemctl status ${SERVICE_NAME}${RESET}"
echo -e " Logs: ${CYAN}journalctl -u ${SERVICE_NAME} -f${RESET}" echo -e " Logs: ${CYAN}journalctl -u ${SERVICE_NAME} -f${RESET}"
echo -e " Reiniciar: ${CYAN}systemctl restart ${SERVICE_NAME}${RESET}" echo -e " Reiniciar: ${CYAN}systemctl restart ${SERVICE_NAME}${RESET}"
echo -e " Detener: ${CYAN}systemctl stop ${SERVICE_NAME}${RESET}" echo -e " Detener: ${CYAN}systemctl stop ${SERVICE_NAME}${RESET}"
echo "" echo ""
warn "Cambiá la contraseña del admin desde la interfaz web." echo -e " ${BOLD}Generar Nuevos Backups:${RESET}"
warn "Los archivos .env contienen credenciales sensibles — NO los incluyas en git." echo -e " ${CYAN}bash backup_db.sh${RESET}"
echo "" echo ""
ok "¡Listo! La aplicación está corriendo en http://${IP}:5000" ok "Despliegue finalizado. El sistema se encuentra 100% operativo."
echo "" echo ""
+1
View File
@@ -16,3 +16,4 @@ Pillow>=10.4.0
email_validator>=2.0.0 email_validator>=2.0.0
psycopg2-binary>=2.9.10 psycopg2-binary>=2.9.10
Babel==2.14.0 Babel==2.14.0
gunicorn>=21.2.0