ci: despliegue por rama en <rama>.localesp.es (Gitea Actions + host runner)
This commit is contained in:
Executable
+144
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy.sh — Despliega la rama actual a <slug>.localesp.es
|
||||
#
|
||||
# Estrategia A (entorno por rama):
|
||||
# /opt/localesp-<slug> + systemd unit localesp-<slug> + 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 <slug>.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 <branch>" >&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" <<EOF
|
||||
[Unit]
|
||||
Description=LocaleSP env rama '$BRANCH' ($HOST)
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
WorkingDirectory=$DEST
|
||||
ExecStart=/usr/bin/node server/index.js
|
||||
Restart=on-failure
|
||||
RestartSec=5s
|
||||
Environment=NODE_ENV=production
|
||||
Environment=PORT=$PORT
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
echo "$PORT" > "$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" <<EOF
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name $HOST;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:$PORT;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host \$host;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
else
|
||||
log "vhost existente: $VHOST — forzando proxy_pass -> :$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:-<sin resolver>}', 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)"
|
||||
Reference in New Issue
Block a user