diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml new file mode 100644 index 0000000..b50beab --- /dev/null +++ b/.gitea/workflows/deploy.yaml @@ -0,0 +1,21 @@ +name: Deploy solution + +on: + workflow_dispatch: + inputs: + domain: + description: 'Seleccione destino:' + required: true + default: 'demo.analytics.beyondcx.org' + tag: + description: 'Indique el tag/release' + required: true + default: 'latest' + + + +jobs: + log-the-inputs: + runs-on: ubuntu-latest + steps: + - run: echo "${{ inputs.domain }} con la imagen ${{ inputs.tag }}" diff --git a/.gitea/workflows/example.yaml b/.gitea/workflows/example.yaml new file mode 100644 index 0000000..9c59524 --- /dev/null +++ b/.gitea/workflows/example.yaml @@ -0,0 +1,52 @@ +name: Workflow de prueba + +on: + pull_request_review: + types: [submitted] + + +env: + DOCKER_ORG: beyondcx + BRANCH_NAME: ${{ github.head_ref || github.ref_name }} + +jobs: + Build and push images: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + + - name: Echo the Tag + run: echo "Tag ${{ env.DOCKER_ORG }}/beyondcx:${{ env.BRANCH_NAME }}" + + - name: Login to Registry + uses: docker/login-action@v3 + with: + registry: ${{ secrets.REGISTRY_URL }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PWD }} + + - name: Build frontend and push it to registry + uses: docker/build-push-action@v5 + with: + context: frontend + push: false + tags: ${{ secrets.REGISTRY_URL }}/${{ env.DOCKER_ORG }}/frontend-analytics-demo:${{ env.BRANCH_NAME }} + platforms: linux/amd64 + file: frontend/Dockerfile + + - name: Build backend and push it to registry + uses: docker/build-push-action@v5 + with: + context: backend + push: false + tags: ${{ secrets.REGISTRY_URL }}/${{ env.DOCKER_ORG }}/backend-analytics-demo:${{ env.BRANCH_NAME }} + platforms: linux/amd64 + file: backend/Dockerfile + + diff --git a/.gitea/workflows/tag.yaml b/.gitea/workflows/tag.yaml new file mode 100644 index 0000000..58003fe --- /dev/null +++ b/.gitea/workflows/tag.yaml @@ -0,0 +1,52 @@ +name: Tag Release + +on: + release: + types: [created,edited,published] + +env: + DOCKER_ORG: beyondcx + +jobs: + new images: + runs-on: ubuntu-latest + steps: + - name: Extract Tag Name + uses: olegtarasov/get-tag@v2.1.4 + id: tagName + - name: Show tag + run: echo "$GIT_TAG_NAME"; + + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Buildx + uses: docker/setup-buildx-action@v3 + with: + driver-opts: network=host + + - name: Login to Registry + uses: docker/login-action@v3 + with: + registry: ${{ secrets.REGISTRY_URL }} + username: ${{ secrets.REGISTRY_USER }} + password: ${{ secrets.REGISTRY_PWD }} + + - name: Build frontend and push it to registry + uses: docker/build-push-action@v5 + with: + context: frontend + push: true + tags: ${{ secrets.REGISTRY_URL }}/${{ env.DOCKER_ORG }}/frontend-analytics-demo:${{ steps.tagName.outputs.tag }} + platforms: linux/amd64 + file: frontend/Dockerfile + + - name: Build backend and push it to registry + uses: docker/build-push-action@v5 + with: + context: backend + push: true + tags: ${{ secrets.REGISTRY_URL }}/${{ env.DOCKER_ORG }}/backend-analytics-demo:${{ steps.tagName.outputs.tag }} + platforms: linux/amd64 + file: backend/Dockerfile + diff --git a/backend/.dockerignore b/backend/.dockerignore index 3fda664..e5f44c8 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -11,3 +11,8 @@ build data/output *.zip .DS_Store +*.log +Dockerfile +docker-compose.yml +.env +tests diff --git a/backend/Dockerfile b/backend/Dockerfile index 88c0b4e..09e3efc 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,31 +1,50 @@ -# backend/Dockerfile -FROM python:3.11-slim +# --------------------------- +# Builder stage +# --------------------------- +FROM python:3.13-bookworm AS builder -# Evitar .pyc y buffering -ENV PYTHONDONTWRITEBYTECODE=1 -ENV PYTHONUNBUFFERED=1 +# Solo herramientas necesarias para compilar dependencias +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential && apt-get clean && rm -rf /var/lib/apt/lists/* + +ADD https://astral.sh/uv/install.sh /install.sh +RUN chmod -R 655 /install.sh && /install.sh && rm /install.sh + +ENV PATH="/root/.local/bin:$PATH" WORKDIR /app -# Dependencias del sistema mínimas -RUN apt-get update && apt-get install -y --no-install-recommends \ - build-essential \ - && rm -rf /var/lib/apt/lists/* - -# Copiamos pyproject y lock si lo hubiera +# Copiamos solo archivos de dependencias (mejor cache) COPY pyproject.toml ./ -# Instalamos dependencias -RUN pip install --upgrade pip && \ - pip install . +# Cambiamos pip por uv más moderno y rápido +RUN uv sync + +# --------------------------- +# Runtime stage +# --------------------------- +FROM python:3.13-slim-bookworm AS production -# Copiamos el resto del código (respetando .dockerignore) -COPY . . -# Variables de autenticación básica ENV BASIC_AUTH_USERNAME=admin ENV BASIC_AUTH_PASSWORD=admin +WORKDIR /app + +# Crear usuario no-root +RUN useradd --create-home appuser + +# Copiamos código y producto uv + +COPY . . +COPY --from=builder /app/.venv .venv + +# Cambiar permisos +#RUN chown -R appuser:appuser /app + +#USER appuser +ENV PATH="/app/.venv/bin:$PATH" + EXPOSE 8000 -CMD ["python", "-m", "uvicorn", "beyond_api.main:app", "--host", "0.0.0.0", "--port", "8000"] +CMD ["uvicorn", "beyond_api.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/docker-compose.yml b/docker-compose.yml index d7734af..c3523a5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: backend: build: context: ./backend - container_name: beyond-backend + container_name: XXX-backend environment: # credenciales del API (las mismas que usas ahora) BASIC_AUTH_USERNAME: "beyond" @@ -15,39 +15,37 @@ services: expose: - "8000" networks: - - beyond-net + - XXX-beyond-net - frontend: + XXXfrontend: build: context: ./frontend args: - # el front compilará con este BASE_URL -> /api + # el front compilar con este BASE_URL -> /api VITE_API_BASE_URL: /api - container_name: beyond-frontend + container_name: XXX-frontend expose: - "4173" networks: - - beyond-net - - nginx: - image: nginx:1.27-alpine - container_name: beyond-nginx + - XXX-beyond-net + - frontend + labels: + - "traefik.enable=true" + - "traefik.http.routers.XXX-server.rule=Host(`DDD`)" + - "traefik.http.routers.XXX-server.entrypoints=websecure" + - "traefik.http.routers.XXX-server.tls=true" + - "traefik.http.routers.XXX-server.tls.certresolver=doresolv" + - "traefik.http.routers.XXX-server.service=XXXfrontend" + - "traefik.http.services.XXXfrontend.loadBalancer.server.port=4173" depends_on: - backend - - frontend - ports: - - "80:80" - - "443:443" - volumes: - - /etc/letsencrypt:/etc/letsencrypt:ro - - ./nginx/conf.d:/etc/nginx/conf.d:ro - networks: - - beyond-net volumes: cache-data: driver: local networks: - beyond-net: + XXX-beyond-net: driver: bridge + frontend: + external: true diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 8853524..6858d52 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -20,17 +20,22 @@ ENV VITE_API_BASE_URL=${VITE_API_BASE_URL} # Construimos el bundle RUN npm run build -# 2) Fase de servidor estático +# 2) Fase de servidor estático. Tenemos que revisar FROM node:20-alpine +#FROM nginx:alpine WORKDIR /app +#RUN rm -rf /usr/share/nginx/html/* # Copiamos el build +#COPY --from=build /app/dist /usr/share/nginx/html COPY --from=build /app/dist ./dist # Server estático muy simple RUN npm install -g serve +#EXPOSE 80 EXPOSE 4173 +#CMD ["nginx", "-g", "daemon off;"] CMD ["serve", "-s", "dist", "-l", "4173"] diff --git a/generate_compose.sh b/generate_compose.sh new file mode 100755 index 0000000..993ecdf --- /dev/null +++ b/generate_compose.sh @@ -0,0 +1,114 @@ +#!/usr/bin/env bash +set -euo pipefail + +############################################### +# CONFIGURACIÓN BÁSICA – EDITA ESTO +############################################### +# TODO: pon aquí la URL real de tu repo (sin credenciales) +REPO_URL_DEFAULT="ssh://git@git.beyondcx.org:2424/susana/BeyondCXAnalytics-Demo.git" +INSTALL_BASE="/opt/beyondcx" + +############################################### +# UTILIDADES +############################################### +step() { + echo + echo "==================================================" + echo " 👉 $1" + echo "==================================================" +} + + +############################################### +# 1. COMPROBACIONES INICIALES +############################################### + +step "Recogiendo datos de configuración" + +read -rp "Dominio para la aplicación (ej: app.cliente.com): " DOMAIN +if [ -z "$DOMAIN" ]; then + echo "El dominio no puede estar vacío." + exit 1 +fi + +SUBDOMAIN=${DOMAIN%%.*} +if [[ $DOMAIN == $SUBDOMAIN ]]; then + DOMAIN=$DOMAIN".analytics.beyondcx.org" +fi +echo "el dominio es $DOMAIN y el subdominio $SUBDOMAIN" + +INSTALL_DIR=$INSTALL_BASE"/"$SUBDOMAIN + +read -rp "Usuario de acceso (Basic Auth / login): " API_USER +if [ -z "$API_USER" ]; then + echo "El usuario no puede estar vacío." + exit 1 +fi + +read -rsp "Contraseña de acceso: " API_PASS +echo +if [ -z "$API_PASS" ]; then + echo "La contraseña no puede estar vacía." + exit 1 +fi + +echo +echo "Resumen de configuración:" +echo " Dominio: $DOMAIN" +echo " Usuario API: $API_USER" + +read -rp "¿Continuar con la instalación? [s/N]: " CONFIRM +CONFIRM=${CONFIRM:-N} +if [[ ! "$CONFIRM" =~ ^[sS]$ ]]; then + echo "Instalación cancelada." + exit 0 +fi + +# COMENZAMOS ... + +############################################### +# 4. CONFIGURAR docker-compose.yml (credenciales y nginx) +############################################### +step "Aplicando credenciales al docker-compose.yml" + +if ! grep -q "BASIC_AUTH_USERNAME" docker-compose.yml; then + echo "⚠ No encuentro BASIC_AUTH_USERNAME en docker-compose.yml. Revisa el archivo a mano." +else + sed -i "s/BASIC_AUTH_USERNAME:.*/BASIC_AUTH_USERNAME: \"$API_USER\"/" docker-compose.yml +fi + +if ! grep -q "BASIC_AUTH_PASSWORD" docker-compose.yml; then + echo "⚠ No encuentro BASIC_AUTH_PASSWORD en docker-compose.yml. Revisa el archivo a mano." +else + sed -i "s/BASIC_AUTH_PASSWORD:.*/BASIC_AUTH_PASSWORD: \"$API_PASS\"/" docker-compose.yml +fi + +if ! grep -q "XXX" docker-compose.yml; then + echo "⚠ No encuentro XXX en docker-compose.yml. Revisa el archivo a mano." +else + sed -i "s/XXX/$SUBDOMAIN/g" docker-compose.yml +fi +if [[ $DOMAIN == $SUBDOMAIN ]]; then + set DOMAIN=$DOMAIN".analytics.beyondcx.org" +fi +if ! grep -q "DDD" docker-compose.yml; then + echo "⚠ No encuentro DDD en docker-compose.yml. Revisa el archivo a mano." +else + sed -i "s/DDD/$DOMAIN/" docker-compose.yml +fi + + + +step "Arrancando contenedores" + +docker compose up -d + +############################################### +# 8. FIN +############################################### +step "Instalación completada" + +echo "La aplicación debería estar disponible en: https://$DOMAIN" +echo +echo "Servicios levantados:" +docker compose ps diff --git a/install_beyond.sh b/install_beyond.sh index fa53ddc..74a5e83 100644 --- a/install_beyond.sh +++ b/install_beyond.sh @@ -5,8 +5,8 @@ set -euo pipefail # CONFIGURACIÓN BÁSICA – EDITA ESTO ############################################### # TODO: pon aquí la URL real de tu repo (sin credenciales) -REPO_URL_DEFAULT="https://github.com/igferne/Beyond-Diagnosis.git" -INSTALL_DIR="/opt/beyonddiagnosis" +REPO_URL_DEFAULT="ssh://git@git.beyondcx.org:2424/susana/BeyondCXAnalytics-Demo.git" +INSTALL_BASE="/opt/beyondcx" ############################################### # UTILIDADES @@ -38,11 +38,13 @@ if [ -z "$DOMAIN" ]; then exit 1 fi -read -rp "Email para Let's Encrypt (avisos de renovación): " EMAIL -if [ -z "$EMAIL" ]; then - echo "El email no puede estar vacío." - exit 1 +SUBDOMAIN=${DOMAIN%%.*} +if [[ $DOMAIN == $SUBDOMAIN ]]; then + DOMAIN=$DOMAIN".analytics.beyondcx.org" fi +echo "el dominio es $DOMAIN y el subdominio $SUBDOMAIN" + +INSTALL_DIR=$INSTALL_BASE"/"$SUBDOMAIN read -rp "Usuario de acceso (Basic Auth / login): " API_USER if [ -z "$API_USER" ]; then @@ -61,41 +63,14 @@ echo read -rp "URL del repositorio Git (HTTPS, sin credenciales) [$REPO_URL_DEFAULT]: " REPO_URL REPO_URL=${REPO_URL:-$REPO_URL_DEFAULT} -echo -read -rp "¿El repositorio es PRIVADO en GitHub y necesitas token? [s/N]: " IS_PRIVATE -IS_PRIVATE=${IS_PRIVATE:-N} - GIT_CLONE_URL="$REPO_URL" -if [[ "$IS_PRIVATE" =~ ^[sS]$ ]]; then - echo "Introduce un Personal Access Token (PAT) de GitHub con permiso de lectura del repo." - read -rsp "GitHub PAT: " GITHUB_TOKEN - echo - if [ -z "$GITHUB_TOKEN" ]; then - echo "El token no puede estar vacío si el repo es privado." - exit 1 - fi - - # Construimos una URL del tipo: https://TOKEN@github.com/usuario/repo.git - if [[ "$REPO_URL" =~ ^https:// ]]; then - GIT_CLONE_URL="https://${GITHUB_TOKEN}@${REPO_URL#https://}" - else - echo "La URL del repositorio debe empezar por https:// para usar el token." - exit 1 - fi -fi echo echo "Resumen de configuración:" echo " Dominio: $DOMAIN" -echo " Email Let'sEnc: $EMAIL" echo " Usuario API: $API_USER" echo " Repo (visible): $REPO_URL" -if [[ "$IS_PRIVATE" =~ ^[sS]$ ]]; then - echo " Repo privado: Sí (se usará un PAT sólo para el clon inicial)" -else - echo " Repo privado: No" -fi -echo +echo " Path del despliegue: $INSTALL_DIR" read -rp "¿Continuar con la instalación? [s/N]: " CONFIRM CONFIRM=${CONFIRM:-N} @@ -104,70 +79,20 @@ if [[ ! "$CONFIRM" =~ ^[sS]$ ]]; then exit 0 fi -############################################### -# 2. INSTALAR DOCKER + DOCKER COMPOSE + CERTBOT -############################################### -step "Instalando Docker, docker compose plugin y certbot" - -apt-get update -y - -# Dependencias para repositorio Docker -apt-get install -y \ - ca-certificates \ - curl \ - gnupg \ - lsb-release - -# Clave GPG de Docker -if [ ! -f /etc/apt/keyrings/docker.gpg ]; then - install -m 0755 -d /etc/apt/keyrings - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ - gpg --dearmor -o /etc/apt/keyrings/docker.gpg -fi - -# Repo Docker estable -if [ ! -f /etc/apt/sources.list.d/docker.list ]; then - echo \ - "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ - $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ - tee /etc/apt/sources.list.d/docker.list > /dev/null -fi - -apt-get update -y - -apt-get install -y \ - docker-ce \ - docker-ce-cli \ - containerd.io \ - docker-buildx-plugin \ - docker-compose-plugin \ - git \ - certbot - -systemctl enable docker -systemctl start docker - -# Abrimos puertos en ufw si está activo -if command -v ufw >/dev/null 2>&1; then - if ufw status | grep -q "Status: active"; then - step "Configurando firewall (ufw) para permitir 80 y 443" - ufw allow 80/tcp || true - ufw allow 443/tcp || true - fi -fi - -############################################### -# 3. CLONAR / ACTUALIZAR REPO -############################################### +# COMENZAMOS ... step "Descargando/actualizando el repositorio en $INSTALL_DIR" +eval $(ssh-agent -s) +ssh-add /home/garbelo/.ssh/id_ed25519 + if [ -d "$INSTALL_DIR/.git" ]; then echo "Directorio git ya existe, haciendo 'git pull'..." git -C "$INSTALL_DIR" pull --ff-only else rm -rf "$INSTALL_DIR" echo "Clonando repositorio..." - git clone "$GIT_CLONE_URL" "$INSTALL_DIR" +# git clone "$GIT_CLONE_URL" "$INSTALL_DIR" + git clone -b proxy "$GIT_CLONE_URL" "$INSTALL_DIR" fi cd "$INSTALL_DIR" @@ -189,97 +114,22 @@ else sed -i "s/BASIC_AUTH_PASSWORD:.*/BASIC_AUTH_PASSWORD: \"$API_PASS\"/" docker-compose.yml fi -# Aseguramos que nginx exponga también 443 -if grep -q 'ports:' docker-compose.yml && grep -q 'nginx:' docker-compose.yml; then - if ! grep -q '443:443' docker-compose.yml; then - sed -i '/- "80:80"/a\ - "443:443"' docker-compose.yml || true - fi -fi - -# Aseguramos que montamos /etc/letsencrypt dentro del contenedor de nginx -if ! grep -q '/etc/letsencrypt:/etc/letsencrypt:ro' docker-compose.yml; then - sed -i '/nginx:/,/networks:/{ - /volumes:/a\ - /etc/letsencrypt:/etc/letsencrypt:ro - }' docker-compose.yml || true -fi - -############################################### -# 5. OBTENER CERTIFICADO LET'S ENCRYPT -############################################### -step "Obteniendo certificado SSL de Let’s Encrypt para $DOMAIN" - -if [ -f "/etc/letsencrypt/live/$DOMAIN/fullchain.pem" ]; then - echo "Certificado ya existe, saltando paso de emisión." +if ! grep -q "XXX" docker-compose.yml; then + echo "⚠ No encuentro XXX en docker-compose.yml. Revisa el archivo a mano." else - # Asegurarnos de que no hay nada escuchando en 80/443 - systemctl stop nginx || true - - certbot certonly \ - --standalone \ - --non-interactive \ - --agree-tos \ - -m "$EMAIL" \ - -d "$DOMAIN" - - echo "Certificado emitido en /etc/letsencrypt/live/$DOMAIN/" + sed -i "s/XXX/$SUBDOMAIN/g" docker-compose.yml +fi +if [[ $DOMAIN == $SUBDOMAIN ]]; then + set DOMAIN=$DOMAIN".analytics.beyondcx.org" +fi +if ! grep -q "DDD" docker-compose.yml; then + echo "⚠ No encuentro DDD en docker-compose.yml. Revisa el archivo a mano." +else + sed -i "s/DDD/$DOMAIN/" docker-compose.yml fi -############################################### -# 6. CONFIGURAR NGINX DENTRO DEL REPO -############################################### -step "Generando configuración nginx con SSL" -mkdir -p nginx/conf.d -cat > nginx/conf.d/beyond.conf < /dev/null +fi + +apt-get update -y + +apt-get install -y \ + docker-ce \ + docker-ce-cli \ + containerd.io \ + docker-buildx-plugin \ + docker-compose-plugin \ + git + +systemctl enable docker +systemctl start docker + +# Abrimos puertos en ufw si está activo +if command -v ufw >/dev/null 2>&1; then + if ufw status | grep -q "Status: active"; then + step "Configurando firewall (ufw) para permitir 80 y 443" + ufw allow 80/tcp || true + ufw allow 443/tcp || true + fi +fi + +# Creamos carpeta del proxy con docker compose. + +mkdir -p /opt/beyondcx/traefik + +cat > /opt/beyondcx/traefik/docker-compose.yaml < /dev/null 2>&1; then + echo "red de traefik existe" +else + docker network create $PROXY_NETWORK +fi + +docker compose up -d + +step "Levantado traefik " + +docker compose ps +docker compose logs + +step "Recuerda, tienes que crear el DO_AUTH_TOKEN en el .env" + + +