#!/usr/bin/env bash # ============================================================================== # install.sh — Instalador automático de Admin Edu-Space # Sistema operativo: Debian 12 (Bookworm) — LXC / Bare Metal # Uso: sudo bash install.sh # Debe ejecutarse desde el directorio raíz del proyecto (donde está app.py) # ============================================================================== set -euo pipefail # ── Colores ───────────────────────────────────────────────────────────────── RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' step() { echo -e "\n${CYAN}${BOLD}▶ $*${NC}"; } ok() { echo -e "${GREEN} ✔ $*${NC}"; } warn() { echo -e "${YELLOW} ⚠ $*${NC}"; } die() { echo -e "${RED} ✘ $*${NC}" >&2; exit 1; } # ── Verificaciones previas ─────────────────────────────────────────────────── [[ $EUID -ne 0 ]] && die "Este script debe ejecutarse como root. Usá: sudo bash install.sh" [[ ! -f "app.py" ]] && die "No se encontró app.py. Ejecutá el script desde el directorio raíz del proyecto." APP_DIR="$(pwd)" APP_USER="eduspace" SERVICE_NAME="admin-edu-space" VENV_DIR="$APP_DIR/venv" PYTHON_BIN="python3" # ── Banner ─────────────────────────────────────────────────────────────────── echo -e "${BOLD}" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ Admin Edu-Space — Instalador para Debian 12 ║" echo "╚══════════════════════════════════════════════════════════════╝" echo -e "${NC}" echo " Directorio de instalación: ${CYAN}$APP_DIR${NC}" # ── 1. Locale UTF-8 ───────────────────────────────────────────────────────── step "Configurando locale UTF-8..." export DEBIAN_FRONTEND=noninteractive apt-get install -y locales > /dev/null 2>&1 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 locale-gen en_US.UTF-8 es_AR.UTF-8 > /dev/null 2>&1 update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 > /dev/null 2>&1 export LANG=en_US.UTF-8 export LC_ALL=en_US.UTF-8 export PYTHONUTF8=1 export PYTHONIOENCODING=utf-8 ok "Locale configurado: en_US.UTF-8" # ── 2. Dependencias del sistema ────────────────────────────────────────────── step "Actualizando repositorios e instalando dependencias del sistema..." apt-get update -qq apt-get install -y \ python3 \ python3-venv \ python3-pip \ python3-dev \ build-essential \ libpq-dev \ postgresql \ postgresql-client \ curl \ git \ > /dev/null 2>&1 ok "Dependencias instaladas (Python3 + PostgreSQL + build tools)" # ── 3. Iniciar PostgreSQL ──────────────────────────────────────────────────── step "Iniciando servicio PostgreSQL..." # Intentar iniciar por distintos métodos (LXC puede no tener systemd activo aún) pg_ctlcluster 15 main start 2>/dev/null \ || service postgresql start 2>/dev/null \ || systemctl start postgresql 2>/dev/null \ || true sleep 2 # Habilitar para inicio automático (ignorar error si ya está habilitado) systemctl enable postgresql > /dev/null 2>&1 || true ok "PostgreSQL activo" # ── 4. Configurar variables de entorno (.env) ──────────────────────────────── step "Configurando variables de entorno..." SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(32))') echo "" echo -e " ${BOLD}Configuración de la base de datos PostgreSQL${NC}" read -rp " Contraseña para el usuario de BD [default: eduspace2024]: " DB_PASS_INPUT DB_PASSWORD="${DB_PASS_INPUT:-eduspace2024}" DB_NAME="classrooms_db" DB_USER="eduspace_user" if [[ -f "$APP_DIR/.env" ]]; then warn ".env ya existe — se conserva sin modificar" # Leer variables del .env existente source <(grep -v '^#' "$APP_DIR/.env" | grep '=' | sed 's/ *= */=/;s/^/export /') # Reasignar desde el .env leído DB_USER="${DB_USERNAME:-$DB_USER}" DB_PASSWORD="${DB_PASSWORD:-eduspace2024}" DB_NAME=$(echo "${DATABASE_URL:-}" | sed 's|.*\/||') DB_NAME="${DB_NAME:-classrooms_db}" else cat > "$APP_DIR/.env" </dev/null \ || warn "El usuario '${DB_USER}' ya existe — se omite creación" sudo -u postgres psql -c "CREATE DATABASE ${DB_NAME} OWNER ${DB_USER};" 2>/dev/null \ || warn "La base de datos '${DB_NAME}' ya existe — se omite creación" sudo -u postgres psql -d "${DB_NAME}" -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 "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}'" # ── 6. Entorno virtual Python + dependencias ───────────────────────────────── step "Creando entorno virtual Python e instalando dependencias..." # Eliminar venv roto si existe de una instalación anterior fallida if [[ -d "$VENV_DIR" ]]; then warn "Venv previo detectado — eliminando para recrear limpio..." rm -rf "$VENV_DIR" fi PY_VERSION=$($PYTHON_BIN -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') ok "Versión de Python: $PY_VERSION" $PYTHON_BIN -m venv "$VENV_DIR" > /dev/null 2>&1 "$VENV_DIR/bin/pip" install --upgrade pip setuptools wheel --quiet "$VENV_DIR/bin/pip" install gunicorn --quiet # Instalar dependencias — con fallback para Python 3.13+ donde algunos paquetes # requieren --no-build-isolation por incompatibilidades con setuptools nuevo if "$VENV_DIR/bin/pip" install -r "$APP_DIR/requirements.txt" --quiet 2>/dev/null; then ok "Dependencias instaladas correctamente" else warn "Instalación estándar falló — reintentando con --no-build-isolation..." "$VENV_DIR/bin/pip" install -r "$APP_DIR/requirements.txt" --no-build-isolation --quiet ok "Dependencias instaladas (modo compatibilidad)" fi ok "Virtualenv listo con todas las dependencias + Gunicorn" # ── 7. Inicializar base de datos ────────────────────────────────────────────── step "Inicializando esquema y datos iniciales (init_db.py)..." cd "$APP_DIR" # Exportar variables del .env para que Flask/SQLAlchemy las use set -a source "$APP_DIR/.env" set +a LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 \ PYTHONUTF8=1 \ PYTHONIOENCODING=utf-8 \ FLASK_APP=app.py \ "$VENV_DIR/bin/python" -X utf8 init_db.py ok "Esquema y datos iniciales cargados correctamente" cd - > /dev/null # ── 8. Usuario del sistema ──────────────────────────────────────────────────── step "Creando usuario del sistema '${APP_USER}'..." if id "$APP_USER" &>/dev/null; then warn "El usuario '${APP_USER}' ya existe — se omite" else useradd --system --no-create-home --shell /usr/sbin/nologin "$APP_USER" ok "Usuario '${APP_USER}' creado" fi chown -R "$APP_USER":"$APP_USER" "$APP_DIR" chmod -R 755 "$APP_DIR" # El .env debe ser legible sólo por el owner chmod 640 "$APP_DIR/.env" ok "Permisos aplicados en $APP_DIR" # ── 9. Servicio systemd ─────────────────────────────────────────────────────── step "Registrando servicio systemd '${SERVICE_NAME}'..." mkdir -p "/var/log/${SERVICE_NAME}" chown -R "$APP_USER":"$APP_USER" "/var/log/${SERVICE_NAME}" cat > "/etc/systemd/system/${SERVICE_NAME}.service" < /dev/null 2>&1 systemctl start "$SERVICE_NAME" sleep 3 if systemctl is-active --quiet "$SERVICE_NAME"; then ok "Servicio '${SERVICE_NAME}' corriendo y habilitado para el arranque automático" else warn "El servicio no inició correctamente. Revisá con:" warn " journalctl -u ${SERVICE_NAME} -n 60 --no-pager" fi # ── 10. Resumen final ───────────────────────────────────────────────────────── IP=$(hostname -I | awk '{print $1}') echo "" echo -e "${BOLD}${GREEN}" echo "╔══════════════════════════════════════════════════════════════╗" echo "║ ✔ Instalación completada con éxito ║" echo "╚══════════════════════════════════════════════════════════════╝" echo -e "${NC}" echo -e " ${BOLD}URL de la aplicación:${NC} http://${IP}:5000" echo "" echo -e " ${BOLD}Credenciales por defecto:${NC}" echo -e " Email: admin@edu-space.com" echo -e " Password: admin123" echo "" echo -e " ${BOLD}Base de datos:${NC}" echo -e " Host: localhost:5432" echo -e " Base: ${DB_NAME}" echo -e " Usuario: ${DB_USER}" echo "" echo -e " ${BOLD}Comandos de gestión:${NC}" echo -e " Ver estado: ${CYAN}systemctl status ${SERVICE_NAME}${NC}" echo -e " Ver logs: ${CYAN}journalctl -u ${SERVICE_NAME} -f${NC}" echo -e " Reiniciar: ${CYAN}systemctl restart ${SERVICE_NAME}${NC}" echo -e " Detener: ${CYAN}systemctl stop ${SERVICE_NAME}${NC}" echo "" echo -e " ${YELLOW}⚠ Cambiá la contraseña del admin desde la interfaz web${NC}" echo ""