fix: rewrite install.sh con deteccion de Python y soporte Pillow en Python 3.13

This commit is contained in:
2026-09-04 21:15:18 -03:00
parent 957f364cc3
commit c7ce5e4e67
+158 -137
View File
@@ -1,111 +1,124 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ============================================================================== # ==============================================================================
# install.sh — Instalador automático de Admin Edu-Space # install.sh — Admin Edu-Space (UniCABA)
# Sistema operativo: Debian 12 (Bookworm) — LXC / Bare Metal # Instalador automatizado para Debian 12 (LXC / Bare Metal)
# Uso: sudo bash install.sh # Uso: sudo bash install.sh
# Debe ejecutarse desde el directorio raíz del proyecto (donde está app.py)
# ============================================================================== # ==============================================================================
set -euo pipefail set -euo pipefail
# ── Colores ───────────────────────────────────────────────────────────────── # ─── Colores ───────────────────────────────────────────────────────────────────
RED='\033[0;31m' RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
GREEN='\033[0;32m' CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'
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} ✔ $*${RESET}"; }
ok() { echo -e "${GREEN} ✔ $*${NC}"; } info() { echo -e "${CYAN} ▸ $*${RESET}"; }
warn() { echo -e "${YELLOW} ⚠ $*${NC}"; } warn() { echo -e "${YELLOW} ⚠ $*${RESET}"; }
die() { echo -e "${RED} ✘ $*${NC}" >&2; exit 1; } err() { echo -e "${RED} ✘ $*${RESET}" >&2; exit 1; }
hdr() { echo -e "\n${BOLD}${CYAN}══ $* ══${RESET}\n"; }
# ── Verificaciones previas ─────────────────────────────────────────────────── # ─── Variables ─────────────────────────────────────────────────────────────────
[[ $EUID -ne 0 ]] && die "Este script debe ejecutarse como root. Usá: sudo bash install.sh" # APP_DIR resuelto desde la ubicación del script (no del CWD)
[[ ! -f "app.py" ]] && die "No se encontró app.py. Ejecutá el script desde el directorio raíz del proyecto." APP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
VENV_DIR="${APP_DIR}/venv"
APP_DIR="$(pwd)" APP_ENV="${APP_DIR}/.env"
APP_USER="eduspace"
SERVICE_NAME="admin-edu-space" SERVICE_NAME="admin-edu-space"
VENV_DIR="$APP_DIR/venv" APP_USER="eduspace"
PYTHON_BIN="python3" PYTHON_REQUIRED_MAJOR=3
PYTHON_REQUIRED_MINOR=10
LOG_DIR="/var/log/${SERVICE_NAME}"
# ── Banner ─────────────────────────────────────────────────────────────────── # ─── Root check ────────────────────────────────────────────────────────────────
echo -e "${BOLD}" [[ $EUID -ne 0 ]] && err "Ejecutá este script con sudo: sudo bash install.sh"
[[ ! -f "${APP_DIR}/app.py" ]] && err "No se encontró app.py en ${APP_DIR}. ¿Estás en el directorio correcto?"
echo -e "\n${BOLD}${CYAN}"
echo "╔══════════════════════════════════════════════════════════════╗" echo "╔══════════════════════════════════════════════════════════════╗"
echo "║ Admin Edu-Space — Instalador para Debian 12 ║" echo "║ Admin Edu-Space (UniCABA) — Instalador Debian 12 ║"
echo "╚══════════════════════════════════════════════════════════════╝" echo "╚══════════════════════════════════════════════════════════════╝"
echo -e "${NC}" echo -e "${RESET}"
echo " Directorio de instalación: ${CYAN}$APP_DIR${NC}" info "Directorio del proyecto: ${APP_DIR}"
info "Servicio systemd: ${SERVICE_NAME}"
info "Usuario de servicio: ${APP_USER}"
# ── 1. Locale UTF-8 ───────────────────────────────────────────────────────── # ─── 1. Actualizar sistema e instalar base ──────────────────────────────────────
step "Configurando locale UTF-8..." hdr "1. Actualizando sistema"
export DEBIAN_FRONTEND=noninteractive export DEBIAN_FRONTEND=noninteractive
apt-get install -y locales > /dev/null 2>&1 apt-get update -qq
apt-get install -y -qq \
curl wget git ca-certificates gnupg lsb-release \
build-essential libpq-dev \
locales
ok "Paquetes base instalados"
# ─── 2. Configurar locale UTF-8 ────────────────────────────────────────────────
hdr "2. Configurando locale"
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
update-locale LANG=en_US.UTF-8 LC_ALL=en_US.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 LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8 PYTHONUTF8=1 PYTHONIOENCODING=utf-8
export LC_ALL=en_US.UTF-8 ok "Locale configurado: en_US.UTF-8 / es_AR.UTF-8"
export PYTHONUTF8=1
export PYTHONIOENCODING=utf-8
ok "Locale configurado: en_US.UTF-8"
# ── 2. Dependencias del sistema ────────────────────────────────────────────── # ─── 3. Instalar Python 3.10+ ───────────────────────────────────────────────────
step "Actualizando repositorios e instalando dependencias del sistema..." hdr "3. Instalando Python"
apt-get update -qq apt-get install -y -qq python3 python3-venv python3-pip python3-dev > /dev/null 2>&1
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 ──────────────────────────────────────────────────── PY_VERSION=$(python3 --version 2>/dev/null | awk '{print $2}' || echo "0.0")
step "Iniciando servicio PostgreSQL..." PY_MAJOR=$(echo "$PY_VERSION" | cut -d. -f1)
# Intentar iniciar por distintos métodos (LXC puede no tener systemd activo aún) PY_MINOR=$(echo "$PY_VERSION" | cut -d. -f2)
if [[ "$PY_MAJOR" -ge "$PYTHON_REQUIRED_MAJOR" && "$PY_MINOR" -ge "$PYTHON_REQUIRED_MINOR" ]]; then
ok "Python $PY_VERSION disponible"
else
info "Python $PY_VERSION es menor a 3.10 — instalando Python 3.11..."
apt-get install -y -qq python3.11 python3.11-venv python3.11-dev > /dev/null 2>&1
update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.11 1 > /dev/null 2>&1
PY_VERSION=$(python3 --version | awk '{print $2}')
ok "Python $PY_VERSION instalado"
fi
# ─── 4. Instalar PostgreSQL ────────────────────────────────────────────────────
hdr "4. Instalando PostgreSQL"
apt-get install -y -qq postgresql postgresql-client > /dev/null 2>&1
ok "PostgreSQL instalado: $(psql --version | awk '{print $3}')"
# Iniciar PostgreSQL (LXC puede no tener systemd activo aún)
pg_ctlcluster 15 main start 2>/dev/null \ pg_ctlcluster 15 main start 2>/dev/null \
|| service postgresql start 2>/dev/null \ || service postgresql start 2>/dev/null \
|| systemctl start postgresql 2>/dev/null \ || systemctl start postgresql 2>/dev/null \
|| true || true
sleep 2 sleep 2
# Habilitar para inicio automático (ignorar error si ya está habilitado)
systemctl enable postgresql > /dev/null 2>&1 || true systemctl enable postgresql > /dev/null 2>&1 || true
ok "PostgreSQL activo" ok "PostgreSQL activo y habilitado al inicio"
# ── 4. Configurar variables de entorno (.env) ──────────────────────────────── # ─── 5. Configurar .env ────────────────────────────────────────────────────────
step "Configurando variables de entorno..." hdr "5. 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_NAME="classrooms_db"
DB_USER="eduspace_user" DB_USER="eduspace_user"
if [[ -f "$APP_DIR/.env" ]]; then echo ""
warn ".env ya existe — se conserva sin modificar" echo -e " ${BOLD}Configuración de la base de datos PostgreSQL${RESET}"
# Leer variables del .env existente read -rp " Contraseña para el usuario de BD [default: eduspace2024]: " DB_PASS_INPUT
source <(grep -v '^#' "$APP_DIR/.env" | grep '=' | sed 's/ *= */=/;s/^/export /') DB_PASSWORD="${DB_PASS_INPUT:-eduspace2024}"
# Reasignar desde el .env leído
if [[ -f "$APP_ENV" ]]; then
ok ".env ya existe — se conserva sin modificar"
# Leer variables del .env existente para usarlas en el script
set -a
# shellcheck disable=SC1090
source <(grep -v '^#' "$APP_ENV" | grep '=')
set +a
DB_USER="${DB_USERNAME:-$DB_USER}" DB_USER="${DB_USERNAME:-$DB_USER}"
DB_PASSWORD="${DB_PASSWORD:-eduspace2024}" DB_PASSWORD="${DB_PASSWORD:-eduspace2024}"
DB_NAME=$(echo "${DATABASE_URL:-}" | sed 's|.*\/||') DB_NAME=$(echo "${DATABASE_URL:-}" | sed 's|.*\/||')
DB_NAME="${DB_NAME:-classrooms_db}" DB_NAME="${DB_NAME:-classrooms_db}"
else else
cat > "$APP_DIR/.env" <<ENVEOF SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(32))')
cat > "$APP_ENV" <<ENVEOF
# Admin Edu-Space — Configuración de producción # Admin Edu-Space — Configuración de producción
# Generado automáticamente por install.sh el $(date '+%Y-%m-%d %H:%M:%S') # Generado automáticamente el $(date '+%Y-%m-%d %H:%M:%S')
FLASK_APP=app.py FLASK_APP=app.py
FLASK_ENV=production FLASK_ENV=production
@@ -121,11 +134,13 @@ API_BASE_URL=http://localhost:8080/api
BABEL_DEFAULT_LOCALE=es BABEL_DEFAULT_LOCALE=es
BABEL_DEFAULT_TIMEZONE=America/Argentina/Buenos_Aires BABEL_DEFAULT_TIMEZONE=America/Argentina/Buenos_Aires
ENVEOF ENVEOF
ok ".env creado con credenciales de producción" chmod 640 "$APP_ENV"
ok ".env creado con SECRET_KEY aleatoria"
warn "Revisá ${APP_ENV} antes de exponer la app a producción"
fi fi
# ── 5. Crear usuario y base de datos PostgreSQL ────────────────────────────── # ─── 6. Crear usuario y base de datos PostgreSQL ────────────────────────────────
step "Configurando usuario y base de datos PostgreSQL..." hdr "6. Configurando base de datos"
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}';" 2>/dev/null \
|| warn "El usuario '${DB_USER}' ya existe — se omite creación" || warn "El usuario '${DB_USER}' ya existe — se omite creación"
@@ -133,45 +148,47 @@ sudo -u postgres psql -c "CREATE USER ${DB_USER} WITH PASSWORD '${DB_PASSWORD}';
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};" 2>/dev/null \
|| warn "La base de datos '${DB_NAME}' ya existe — se omite creación" || 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}" \
sudo -u postgres psql -d "${DB_NAME}" -c "GRANT ALL ON SCHEMA public 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 "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 "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 "Base de datos '${DB_NAME}' lista con usuario '${DB_USER}'"
# ── 6. Entorno virtual Python + dependencias ───────────────────────────────── # ─── 7. Entorno virtual Python + dependencias ───────────────────────────────────
step "Creando entorno virtual Python e instalando dependencias..." hdr "7. Instalando dependencias Python"
# Eliminar venv roto si existe de una instalación anterior fallida # Limpiar venv previo roto si existe
if [[ -d "$VENV_DIR" ]]; then if [[ -d "$VENV_DIR" ]]; then
warn "Venv previo detectado — eliminando para recrear limpio..." warn "Venv previo detectado — eliminando para recrear limpio..."
rm -rf "$VENV_DIR" rm -rf "$VENV_DIR"
fi fi
PY_VERSION=$($PYTHON_BIN -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') python3 -m venv "$VENV_DIR" > /dev/null 2>&1
ok "Versión de Python: $PY_VERSION" "${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}')"
$PYTHON_BIN -m venv "$VENV_DIR" > /dev/null 2>&1 # Instalar requirements con fallback para Python 3.13+
"$VENV_DIR/bin/pip" install --upgrade pip setuptools wheel --quiet info "Instalando requirements.txt..."
"$VENV_DIR/bin/pip" install gunicorn --quiet if "${VENV_DIR}/bin/pip" install -r "${APP_DIR}/requirements.txt" --quiet 2>/dev/null; then
# 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" ok "Dependencias instaladas correctamente"
else else
warn "Instalación estándar falló — reintentando con --no-build-isolation..." warn "Instalación estándar falló — reintentando con --no-build-isolation (Python 3.13+)..."
"$VENV_DIR/bin/pip" install -r "$APP_DIR/requirements.txt" --no-build-isolation --quiet "${VENV_DIR}/bin/pip" install -r "${APP_DIR}/requirements.txt" \
--no-build-isolation --quiet
ok "Dependencias instaladas (modo compatibilidad)" ok "Dependencias instaladas (modo compatibilidad)"
fi fi
ok "Virtualenv listo con todas las dependencias + Gunicorn"
# ── 7. Inicializar base de datos ────────────────────────────────────────────── # ─── 8. Inicializar base de datos ───────────────────────────────────────────────
step "Inicializando esquema y datos iniciales (init_db.py)..." hdr "8. Inicializando base de datos"
cd "$APP_DIR" cd "$APP_DIR"
# Exportar variables del .env para que Flask/SQLAlchemy las use
set -a set -a
source "$APP_DIR/.env" # shellcheck disable=SC1090
source "$APP_ENV"
set +a set +a
LANG=en_US.UTF-8 \ LANG=en_US.UTF-8 \
@@ -179,36 +196,37 @@ LC_ALL=en_US.UTF-8 \
PYTHONUTF8=1 \ PYTHONUTF8=1 \
PYTHONIOENCODING=utf-8 \ PYTHONIOENCODING=utf-8 \
FLASK_APP=app.py \ FLASK_APP=app.py \
"$VENV_DIR/bin/python" -X utf8 init_db.py "${VENV_DIR}/bin/python" -X utf8 init_db.py
ok "Esquema y datos iniciales cargados correctamente" ok "Esquema, roles, edificios y usuario admin inicializados"
cd - > /dev/null cd - > /dev/null
# ── 8. Usuario del sistema ──────────────────────────────────────────────────── # ─── 9. Usuario del sistema ─────────────────────────────────────────────────────
step "Creando usuario del sistema '${APP_USER}'..." hdr "9. Configurando usuario 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" warn "El usuario '${APP_USER}' ya existe — se omite"
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 '${APP_USER}' creado" ok "Usuario de sistema '${APP_USER}' creado (sin shell)"
fi fi
chown -R "$APP_USER":"$APP_USER" "$APP_DIR" chown -R "${APP_USER}:${APP_USER}" "$APP_DIR"
chmod -R 755 "$APP_DIR" chmod -R 755 "$APP_DIR"
# El .env debe ser legible sólo por el owner chmod 640 "$APP_ENV"
chmod 640 "$APP_DIR/.env"
ok "Permisos aplicados en $APP_DIR"
# ── 9. Servicio systemd ─────────────────────────────────────────────────────── mkdir -p "$LOG_DIR"
step "Registrando servicio systemd '${SERVICE_NAME}'..." chown -R "${APP_USER}:${APP_USER}" "$LOG_DIR"
ok "Permisos aplicados en ${APP_DIR}"
mkdir -p "/var/log/${SERVICE_NAME}" # ─── 10. Servicio systemd ───────────────────────────────────────────────────────
chown -R "$APP_USER":"$APP_USER" "/var/log/${SERVICE_NAME}" hdr "10. Configurando servicio systemd"
GUNICORN_BIN="${VENV_DIR}/bin/gunicorn"
cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<SVCEOF cat > "/etc/systemd/system/${SERVICE_NAME}.service" <<SVCEOF
[Unit] [Unit]
Description=Admin Edu-Space — Flask/Gunicorn Application Description=Admin Edu-Space — Flask/Gunicorn (UniCABA)
Documentation=https://github.com/carlostellocba/admin-edu-space Documentation=https://gitea.oemspot.com.ar/carlostellocba/admin-edu-space
After=network.target postgresql.service After=network.target postgresql.service
Wants=postgresql.service Wants=postgresql.service
@@ -217,18 +235,18 @@ Type=simple
User=${APP_USER} User=${APP_USER}
Group=${APP_USER} Group=${APP_USER}
WorkingDirectory=${APP_DIR} WorkingDirectory=${APP_DIR}
EnvironmentFile=${APP_DIR}/.env EnvironmentFile=${APP_ENV}
Environment="LANG=en_US.UTF-8" Environment="LANG=en_US.UTF-8"
Environment="LC_ALL=en_US.UTF-8" Environment="LC_ALL=en_US.UTF-8"
Environment="PYTHONUTF8=1" Environment="PYTHONUTF8=1"
Environment="PYTHONIOENCODING=utf-8" Environment="PYTHONIOENCODING=utf-8"
Environment="FLASK_ENV=production" Environment="FLASK_ENV=production"
ExecStart=${VENV_DIR}/bin/gunicorn \ ExecStart=${GUNICORN_BIN} \
--workers 3 \ --workers 3 \
--bind 0.0.0.0:5000 \ --bind 0.0.0.0:5000 \
--timeout 120 \ --timeout 120 \
--access-logfile /var/log/${SERVICE_NAME}/access.log \ --access-logfile ${LOG_DIR}/access.log \
--error-logfile /var/log/${SERVICE_NAME}/error.log \ --error-logfile ${LOG_DIR}/error.log \
--log-level info \ --log-level info \
app:app app:app
Restart=always Restart=always
@@ -236,47 +254,50 @@ RestartSec=5
StandardOutput=journal StandardOutput=journal
StandardError=journal StandardError=journal
SyslogIdentifier=${SERVICE_NAME} SyslogIdentifier=${SERVICE_NAME}
NoNewPrivileges=true
PrivateTmp=true
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
SVCEOF 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 start "${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 para el arranque automático" ok "Servicio '${SERVICE_NAME}' corriendo y habilitado al inicio"
else else
warn "El servicio no inició correctamente. Revisá con:" warn "El servicio no inició correctamente. Revisá los logs:"
warn " journalctl -u ${SERVICE_NAME} -n 60 --no-pager" warn " journalctl -u ${SERVICE_NAME} -n 60 --no-pager"
fi fi
# ── 10. Resumen final ───────────────────────────────────────────────────────── # ─── Resumen final ──────────────────────────────────────────────────────────────
hdr "Instalación completada"
IP=$(hostname -I | awk '{print $1}') IP=$(hostname -I | awk '{print $1}')
echo -e " ${BOLD}URL de la aplicación:${RESET}"
echo -e " ${CYAN}http://${IP}:5000${RESET}"
echo "" echo ""
echo -e "${BOLD}${GREEN}" echo -e " ${BOLD}Credenciales por defecto:${RESET}"
echo "╔══════════════════════════════════════════════════════════════╗" echo -e " Email: ${CYAN}admin@edu-space.com${RESET}"
echo "║ ✔ Instalación completada con éxito ║" echo -e " Password: ${CYAN}admin123${RESET}"
echo "╚══════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
echo -e " ${BOLD}URL de la aplicación:${NC} http://${IP}:5000"
echo "" echo ""
echo -e " ${BOLD}Credenciales por defecto:${NC}" echo -e " ${BOLD}Base de datos:${RESET}"
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 " Host: localhost:5432"
echo -e " Base: ${DB_NAME}" echo -e " Base: ${DB_NAME}"
echo -e " Usuario: ${DB_USER}" echo -e " Usuario: ${DB_USER}"
echo "" echo ""
echo -e " ${BOLD}Comandos de gestión:${NC}" echo -e " ${BOLD}Comandos de gestión:${RESET}"
echo -e " Ver estado: ${CYAN}systemctl status ${SERVICE_NAME}${NC}" echo -e " Estado: ${CYAN}systemctl status ${SERVICE_NAME}${RESET}"
echo -e " Ver logs: ${CYAN}journalctl -u ${SERVICE_NAME} -f${NC}" echo -e " Logs: ${CYAN}journalctl -u ${SERVICE_NAME} -f${RESET}"
echo -e " Reiniciar: ${CYAN}systemctl restart ${SERVICE_NAME}${NC}" echo -e " Reiniciar: ${CYAN}systemctl restart ${SERVICE_NAME}${RESET}"
echo -e " Detener: ${CYAN}systemctl stop ${SERVICE_NAME}${NC}" echo -e " Detener: ${CYAN}systemctl stop ${SERVICE_NAME}${RESET}"
echo "" echo ""
echo -e " ${YELLOW}⚠ Cambiá la contraseña del admin desde la interfaz web${NC}" warn "Cambiá la contraseña del admin desde la interfaz web."
warn "Los archivos .env contienen credenciales sensibles — NO los incluyas en git."
echo ""
ok "¡Listo! La aplicación está corriendo en http://${IP}:5000"
echo "" echo ""