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:
+33
-8
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -104,84 +104,106 @@ El menú desplegable **Gestión** en la barra de navegación organiza las áreas
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🚀 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`)
|
|
||||||
* Conexión a Internet
|
|
||||||
|
|
||||||
#### Pasos
|
Podés crear el contenedor desde la interfaz Web de Proxmox (*Create CT*) o por terminal Proxmox con `pct create`:
|
||||||
|
|
||||||
|
* **Plantilla (Template):** `debian-12-standard` (*Recomendado*) o `ubuntu-22.04-standard` / `ubuntu-24.04-standard`.
|
||||||
|
* **CPU:** 1 a 2 vCPU.
|
||||||
|
* **Memoria RAM:** 1024 MB a 2048 MB (Swap: 512 MB).
|
||||||
|
* **Disco:** 8 GB a 15 GB (almacenamiento `local-lvm` o `zfs`).
|
||||||
|
* **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`).
|
||||||
|
* **Opciones del Contenedor:**
|
||||||
|
* *Unprivileged container:* Sí (marcado por defecto, compatible).
|
||||||
|
* *Features (Características):* Habilitar **Nesting (`nesting=1`)** en `CT > Opciones > Características` (requerido para systemd).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚡ Paso 2: Instalación Automatizada con `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:
|
||||||
```bash
|
```bash
|
||||||
# 1. Clonar el repositorio
|
pct enter <ID_DEL_CONTENEDOR>
|
||||||
git clone https://gitea.oemspot.com.ar/carlostellocba/admin-edu-space.git
|
|
||||||
cd admin-edu-space
|
|
||||||
|
|
||||||
# 2. Dar permisos de ejecución al instalador
|
|
||||||
chmod +x install.sh
|
|
||||||
|
|
||||||
# 3. Ejecutar el instalador como root
|
|
||||||
sudo bash install.sh
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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).
|
2. **Clonar el repositorio y lanzar el instalador:**
|
||||||
|
```bash
|
||||||
|
apt update && apt install -y git
|
||||||
|
git clone https://gitea.oemspot.com.ar/carlostellocba/admin-edu-space.git
|
||||||
|
cd admin-edu-space
|
||||||
|
bash install.sh
|
||||||
|
```
|
||||||
|
|
||||||
#### Qué instala y configura automáticamente
|
3. **Flujo de Ejecución Automatizado en el LXC:**
|
||||||
|
* **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.
|
||||||
|
* **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.
|
||||||
|
* **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)):**
|
||||||
|
* Detecta el volcado completo de datos.
|
||||||
|
* Crea la base de datos `classrooms_db` y el usuario `eduspace_user`.
|
||||||
|
* 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.
|
||||||
|
* Otorga privilegios totales (`GRANT ALL`) en el esquema `public`, todas las tablas y **todas las secuencias** al usuario `eduspace_user`.
|
||||||
|
* 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.).
|
||||||
|
* **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.
|
||||||
|
|
||||||
| Paso | Acción |
|
---
|
||||||
| :--- | :--- |
|
|
||||||
| 1 | Configura locale `en_US.UTF-8` en el sistema (previene errores de encoding) |
|
|
||||||
| 2 | Instala Python 3, venv, pip, build-tools, libpq-dev y PostgreSQL |
|
|
||||||
| 3 | Inicia y habilita el servicio PostgreSQL para el arranque automático |
|
|
||||||
| 4 | Genera `SECRET_KEY` aleatoria y crea el archivo `.env` de producción |
|
|
||||||
| 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) |
|
|
||||||
| 8 | Crea el usuario del sistema `eduspace` (sin shell, por seguridad) |
|
|
||||||
| 9 | Registra y arranca el servicio systemd `admin-edu-space` en el puerto **5000** |
|
|
||||||
| 10 | Muestra resumen con URL, credenciales y comandos de gestión |
|
|
||||||
|
|
||||||
#### Comandos de gestión post-instalación
|
### 🌐 Paso 3: Acceso Inmediato a la Aplicación
|
||||||
|
|
||||||
|
Al finalizar la instalación, el script mostrará la dirección IP del contenedor:
|
||||||
|
* **URL:** **`http://<IP-DEL-LXC>:5000`** (ejemplo: `http://192.168.1.150:5000`)
|
||||||
|
* **Email Administrador:** `admin@edu-space.com`
|
||||||
|
* **Contraseña Administrador:** `admin123`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🛠️ Comandos de Gestión del Servicio en el LXC
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Ver estado del servicio
|
# Consultar estado en tiempo real
|
||||||
systemctl status admin-edu-space
|
systemctl status admin-edu-space
|
||||||
|
|
||||||
# Ver logs en tiempo real
|
# Seguir logs de la aplicación
|
||||||
journalctl -u admin-edu-space -f
|
journalctl -u admin-edu-space -f
|
||||||
|
|
||||||
# Reiniciar la aplicación
|
# Reiniciar la aplicación
|
||||||
systemctl restart admin-edu-space
|
systemctl restart admin-edu-space
|
||||||
|
|
||||||
# Detener la aplicación
|
# Detener el servicio
|
||||||
systemctl stop admin-edu-space
|
systemctl stop admin-edu-space
|
||||||
```
|
```
|
||||||
|
|
||||||
> **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.
|
---
|
||||||
|
|
||||||
|
### 💾 Generación de Backups y Migración a Otros Contenedores LXC
|
||||||
|
|
||||||
|
Para respaldar la base de datos en ejecución o clonarla a otro contenedor LXC:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dentro del contenedor LXC:
|
||||||
|
cd /opt/admin-edu-space
|
||||||
|
bash backup_db.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
* El script genera un archivo fechado en `backups/classrooms_db_YYYYMMDD_HHMMSS.sql`.
|
||||||
|
* Actualiza automáticamente el archivo principal [classrooms_db.sql](file:///c:/Workspace/admin-edu-space/classrooms_db.sql).
|
||||||
|
* **Para migrar a otro contenedor:** Podés transferir `classrooms_db.sql` mediante SCP o Git y ejecutar `bash install.sh` en el nuevo LXC.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 🖥️ Opción B — Instalación Manual (Desarrollo Local)
|
### 💻 Instalación Manual para Desarrollo Local (con `venv`)
|
||||||
|
|
||||||
#### Prerrequisitos
|
Si deseás correr el proyecto en una máquina de desarrollo (Windows / Linux / macOS) sin usar el instalador de Proxmox:
|
||||||
1. **Python 3.10 o superior**
|
|
||||||
2. **PostgreSQL** en ejecución con una base de datos creada (ej. `classrooms_db`).
|
|
||||||
3. **Git**
|
|
||||||
|
|
||||||
#### Pasos de Configuración
|
1. **Crear y activar el entorno virtual:**
|
||||||
|
|
||||||
1. **Clonar el repositorio:**
|
|
||||||
```bash
|
|
||||||
git clone https://gitea.oemspot.com.ar/carlostellocba/admin-edu-space.git
|
|
||||||
cd admin-edu-space
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Crear y activar el entorno virtual:**
|
|
||||||
* En Windows (PowerShell):
|
* En Windows (PowerShell):
|
||||||
```powershell
|
```powershell
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
@@ -193,39 +215,30 @@ systemctl stop admin-edu-space
|
|||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Instalar dependencias:**
|
2. **Instalar dependencias:**
|
||||||
```bash
|
```bash
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Configurar variables de entorno (`.env`):**
|
3. **Configurar base de datos local y restaurar snapshot:**
|
||||||
Copiar la plantilla y editar con los valores de tu entorno:
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
```
|
|
||||||
```env
|
# Crear base de datos en PostgreSQL local y cargar datos
|
||||||
SECRET_KEY=clave-secreta-de-produccion-cambiar-en-despliegue
|
sudo -u postgres psql -c "CREATE DATABASE classrooms_db;"
|
||||||
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/classrooms_db
|
sudo -u postgres psql -d classrooms_db < classrooms_db.sql
|
||||||
FLASK_ENV=development
|
|
||||||
FLASK_DEBUG=1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **Inicializar la Base de Datos:**
|
4. **Compilar catálogos de traducción:**
|
||||||
Ejecutar el script de inicialización para crear tablas, roles institucionales, permisos RBAC, sedes y el usuario administrador:
|
|
||||||
```bash
|
|
||||||
python init_db.py
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **Compilar catálogos de traducción (opcional si se modifican textos):**
|
|
||||||
```bash
|
```bash
|
||||||
pybabel compile -d translations
|
pybabel compile -d translations
|
||||||
```
|
```
|
||||||
|
|
||||||
7. **Iniciar el servidor de desarrollo:**
|
5. **Iniciar el servidor:**
|
||||||
```bash
|
```bash
|
||||||
python app.py
|
python wsgi.py
|
||||||
```
|
```
|
||||||
La aplicación estará disponible en: **`http://localhost:5000`**
|
Disponible en: **`http://localhost:5000`**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -235,7 +248,7 @@ systemctl stop admin-edu-space
|
|||||||
| :--- | :--- | :--- | :--- |
|
| :--- | :--- | :--- | :--- |
|
||||||
| **Administrador del Sistema** | `admin@edu-space.com` | `admin123` | `Admin` (Acceso Total) |
|
| **Administrador del Sistema** | `admin@edu-space.com` | `admin123` | `Admin` (Acceso Total) |
|
||||||
|
|
||||||
> ⚠️ **Importante:** Cambiá la contraseña del administrador inmediatamente después del primer acceso.
|
> ⚠️ **Seguridad:** Cambiá la contraseña del administrador desde la interfaz web inmediatamente después del primer inicio de sesión.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -271,41 +284,18 @@ admin-edu-space/
|
|||||||
│ │ └── schedule.py # Calendario interactivo, cartelera del día, comisiones y reservas
|
│ │ └── schedule.py # Calendario interactivo, cartelera del día, comisiones y reservas
|
||||||
│ ├── services/ # Servicios y lógica de negocio
|
│ ├── services/ # Servicios y lógica de negocio
|
||||||
│ │ └── sheets_importer.py # Parser y sincronizador bidireccional de Google Sheets
|
│ │ └── sheets_importer.py # Parser y sincronizador bidireccional de Google Sheets
|
||||||
│ ├── static/ # Recursos estáticos web
|
│ ├── static/ # Recursos estáticos web (CSS, JS, imágenes institucionales)
|
||||||
│ │ ├── css/
|
│ └── templates/ # Vistas Jinja2 renderizadas con Bootstrap 5
|
||||||
│ │ │ ├── style.css # Estilos base y utilidades
|
|
||||||
│ │ │ └── theme.css # Variables CSS para modo claro y modo oscuro
|
|
||||||
│ │ └── js/
|
|
||||||
│ │ ├── genetic-algorithm.js # Lógica de ejecución del algoritmo genético
|
|
||||||
│ │ ├── main.js # Interacciones globales y filtros dinámicos
|
|
||||||
│ │ └── theme-toggle.js # Conmutador reactivo de tema con persistencia
|
|
||||||
│ ├── templates/ # Vistas Jinja2 renderizadas con Bootstrap 5
|
|
||||||
│ │ ├── base.html # Plantilla maestra con navbar categorizada y menú Gestión
|
|
||||||
│ │ ├── dashboard.html # Panel principal de control y métricas
|
|
||||||
│ │ ├── genetic_optimizer.html # Interfaz del optimizador de espacios
|
|
||||||
│ │ ├── admin/
|
|
||||||
│ │ │ ├── academic_terms/ # Gestión de Ciclos Lectivos y cuatrimestres
|
|
||||||
│ │ │ ├── careers/ # Gestión de Carreras y titulaciones
|
|
||||||
│ │ │ ├── metrics/ # Dashboard de Ocupación y Métricas de Espacios
|
|
||||||
│ │ │ ├── milestone_types/ # Gestión de Tipificaciones de Hitos
|
|
||||||
│ │ │ ├── roles/ # Gestión de Roles y Matriz de Permisos RBAC
|
|
||||||
│ │ │ ├── subjects/ # Gestión de Asignaturas y Materias
|
|
||||||
│ │ │ ├── users/ # Gestión de Usuarios y cuentas
|
|
||||||
│ │ │ └── import.html # Sincronización con Google Sheets
|
|
||||||
│ │ ├── auth/ # Vistas de autenticación y perfil de usuario
|
|
||||||
│ │ ├── buildings/ # Listado, alta y edición de sedes y edificios
|
|
||||||
│ │ ├── classrooms/ # Listado físico vs virtual, alta, edición y ficha
|
|
||||||
│ │ └── schedule/ # Calendario (FullCalendar), cartelera del día, comisiones y reservas
|
|
||||||
│ └── utils/ # Utilidades transversales y decoradores
|
|
||||||
│ └── decorators.py # Decoradores RBAC (@permission_required, can_manage, etc.)
|
|
||||||
├── config/ # Configuraciones de entorno (desarrollo, testing, prod)
|
├── config/ # Configuraciones de entorno (desarrollo, testing, prod)
|
||||||
├── translations/ # Catálogos GNU gettext compilados (es / en)
|
├── translations/ # Catálogos GNU gettext compilados (es / en)
|
||||||
├── install.sh # Instalador automático para Debian 12 LXC
|
├── classrooms_db.sql # Snapshot completo de base de datos (PostgreSQL 14+)
|
||||||
├── init_db.py # Inicializador DDL y semillero de datos
|
├── install.sh # Instalador automatizado para Proxmox VE LXC (venv + PostgreSQL)
|
||||||
|
├── backup_db.sh # Script para generar nuevos backups y actualizar snapshot
|
||||||
|
├── init_db.py # Inicializador DDL y sincronizador de esquema/roles
|
||||||
├── CHANGELOG_SESSION.md # Historial técnico cronológico
|
├── CHANGELOG_SESSION.md # Historial técnico cronológico
|
||||||
├── requirements.txt # Dependencias Python
|
├── requirements.txt # Dependencias Python (incluye Gunicorn)
|
||||||
├── .env.example # Plantilla de variables de entorno
|
├── .env.example # Plantilla de variables de entorno
|
||||||
└── app.py # Punto de entrada de la aplicación Flask
|
└── wsgi.py # Punto de entrada de la aplicación Flask
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -313,3 +303,4 @@ admin-edu-space/
|
|||||||
## 📄 Licencia
|
## 📄 Licencia
|
||||||
|
|
||||||
Este proyecto forma parte del sistema de gestión institucional y educativa de **UniCABA**. Todos los derechos reservados.
|
Este proyecto forma parte del sistema de gestión institucional y educativa de **UniCABA**. Todos los derechos reservados.
|
||||||
|
␍
|
||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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
File diff suppressed because it is too large
Load Diff
+139
-109
@@ -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)"
|
||||||
|
if [ -f /etc/locale.gen ]; then
|
||||||
sed -i 's/# en_US.UTF-8 UTF-8/en_US.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
|
||||||
sed -i 's/# es_AR.UTF-8 UTF-8/es_AR.UTF-8 UTF-8/' /etc/locale.gen
|
sed -i 's/# es_AR.UTF-8 UTF-8/es_AR.UTF-8 UTF-8/' /etc/locale.gen
|
||||||
locale-gen en_US.UTF-8 es_AR.UTF-8 > /dev/null 2>&1
|
locale-gen en_US.UTF-8 es_AR.UTF-8 > /dev/null 2>&1 || true
|
||||||
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 > /dev/null 2>&1
|
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
|
# Restaurar backup filtrando directivas específicas de PG18 si existiesen
|
||||||
ok "Base de datos restaurada correctamente desde classrooms_db.sql."
|
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
|
else
|
||||||
err "Error al restaurar la base de datos desde el archivo SQL."
|
warn "Restauración completada con avisos menores — aplicando permisos..."
|
||||||
fi
|
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"
|
# 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 ""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user