#!/usr/bin/env bash # deploy.sh — Despliega la rama actual a .localesp.es # # Estrategia A (entorno por rama): # /opt/localesp- + systemd unit localesp- + vhost nginx + cert Let's Encrypt. # Reutiliza el puerto de la unit existente (adopta entornos legacy) o asigna uno libre (>=8082). # # PRECONDICIÓN MANUAL: el registro A .localesp.es debe existir (se gestiona a mano). # Si no resuelve aún, la app queda servida por HTTP y se emite el cert cuando el DNS apunte aquí. # # Ejecutar como root (host runner). Idempotente. set -euo pipefail BRANCH="${1:-${GITHUB_REF_NAME:-}}" [ -n "$BRANCH" ] || { echo "usage: $0 " >&2; exit 2; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" DOMAIN="localesp.es" PORT_BASE=8082 # 8080/8081 son entornos legacy log() { printf '\033[1;34m[deploy]\033[0m %s\n' "$*"; } err() { printf '\033[1;31m[deploy:ERROR]\033[0m %s\n' "$*" >&2; } slugify() { printf '%s' "$1" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g; s/^-+//; s/-+$//' } SLUG="$(slugify "$BRANCH")" [ -n "$SLUG" ] || { err "slug vacío para la rama '$BRANCH'"; exit 2; } DEST="/opt/localesp-$SLUG" UNIT="localesp-$SLUG" HOST="$SLUG.$DOMAIN" # --- asignación de puerto --- allocate_port() { local u="/etc/systemd/system/$UNIT.service" if [ -f "$u" ]; then # adopta entorno existente local p; p=$(grep -oE 'PORT=[0-9]+' "$u" | head -1 | cut -d= -f2 || true) [ -n "$p" ] && { echo "$p"; return; } fi if [ -f "$DEST/.port" ]; then cat "$DEST/.port"; return; fi local used; used=$( { grep -rhoE 'PORT=[0-9]+' /etc/systemd/system/localesp*.service 2>/dev/null | cut -d= -f2; grep -rhoE 'localhost:[0-9]+' /etc/nginx/conf.d/*.conf 2>/dev/null | cut -d: -f2; } | sort -un) local p=$PORT_BASE while printf '%s\n' "$used" | grep -qx "$p"; do p=$((p+1)); done echo "$p" } PORT="$(allocate_port)" log "rama=$BRANCH slug=$SLUG host=$HOST dest=$DEST puerto=$PORT" # --- 1. build artifacts --- [ -d "$ROOT/dist" ] || { err "falta dist/ — ¿se ejecutó el build?"; exit 1; } log "sincronizando artefactos -> $DEST" mkdir -p "$DEST/dist" "$DEST/server" rsync -a --delete "$ROOT/dist/" "$DEST/dist/" rsync -a --delete "$ROOT/server/" "$DEST/server/" cp -a "$ROOT/package.json" "$ROOT/package-lock.json" "$DEST/" # localesp.db se PRESERVA (datos de usuarios). No se toca aquí. # --- 2. dependencias de producción (express + better-sqlite3) --- log "instalando dependencias (prod)" ( cd "$DEST" && npm ci --omit=dev --no-audit --no-fund ) # --- 3. systemd unit --- log "escribiendo unit $UNIT (PORT=$PORT)" cat > "/etc/systemd/system/$UNIT.service" < "$DEST/.port" systemctl daemon-reload systemctl enable "$UNIT" >/dev/null 2>&1 || true systemctl restart "$UNIT" log "$UNIT arrancada" # --- 4. vhost nginx (busca por server_name; crea o reajusta proxy_pass) --- log "configurando nginx para $HOST" HOST_RE="$(printf '%s' "$HOST" | sed 's/\./\\./g')" VHOST="$(grep -rlE "server_name[[:space:]]+$HOST_RE[[:space:]]*;" /etc/nginx/conf.d/*.conf 2>/dev/null | head -1 || true)" if [ -z "$VHOST" ]; then VHOST="/etc/nginx/conf.d/$SLUG.conf" log "creando vhost HTTP $VHOST" cat > "$VHOST" < :$PORT" sed -i -E "s|proxy_pass http://localhost:[0-9]+;|proxy_pass http://localhost:$PORT;|g" "$VHOST" fi nginx -t 2>&1 | tail -2 systemctl reload nginx log "nginx recargado" # --- 5. TLS (sólo si no hay cert y el DNS ya apunta aquí) --- if [ -d "/etc/letsencrypt/live/$HOST" ]; then log "cert TLS ya presente para $HOST" else log "comprobando DNS de $HOST" PUBIP="$(curl -s4 --max-time 5 ifconfig.me || true)" RESOLVED="$(getent hosts "$HOST" | awk '{print $1}' | head -1 || true)" if [ -n "$PUBIP" ] && [ "$RESOLVED" = "$PUBIP" ]; then log "emitiendo cert con certbot --nginx" if certbot --nginx -d "$HOST" -n --redirect --keep-until-expiring; then log "cert emitido ✓" else err "certbot falló; $HOST sigue en HTTP. Revisa y vuelve a lanzar el workflow." fi else err "DNS de $HOST -> '${RESOLVED:-}', esperado $PUBIP." err "Crea el registro A $HOST -> $PUBIP y, tras propagar, re-lanza el workflow" err "(o ejecuta: certbot --nginx -d $HOST). La app ya vive en http://$HOST" fi fi PROTO=https; [ ! -d "/etc/letsencrypt/live/$HOST" ] && PROTO=http log "LISTO: $PROTO://$HOST (rama=$BRANCH unit=$UNIT puerto=$PORT)"