#!/usr/bin/env bash # deploy.sh — Quelque part en 2035 (quartier.trans-former.fr), site statique. # # Pipeline : build LOCAL (aucun binaire natif : le build x86_64 sert un VPS arm64 sans problème) # → tar de dist/ → scp → /opt/quartier/releases// → bascule du lien /opt/quartier/current # → smoke LOCAL sur le VPS (Host: quartier.trans-former.fr, marqueur exigé, bloquant) # → smoke public (informatif tant que DNS/certificat ne sont pas là) → garde 3 releases. # # Usage : ./deploy.sh (déploie HEAD ; refuse un arbre non commité) # ./deploy.sh --force (déploie l'arbre courant même non commité, marqueur suffixé -dirty) # Retour arrière : ssh vps-hetzner 'ln -sfn /opt/quartier/releases/ /opt/quartier/current' # ⚠ Ne JAMAIS lancer depuis une copie du repo dans Dropbox. set -euo pipefail VPS_HOST="${VPS_HOST:-vps-hetzner}" DEST="/opt/quartier" DOMAINE="quartier.trans-former.fr" GARDER=3 case "$(pwd)" in *Dropbox*) echo "ÉCHEC : jamais depuis Dropbox"; exit 1;; esac HASH="$(git rev-parse --short HEAD)" if git status --porcelain | grep -q .; then if [ "${1:-}" = "--force" ]; then HASH="${HASH}-dirty"; else echo "ÉCHEC : arbre de travail non propre. Commit d'abord, ou --force (marqueur ${HASH}-dirty)."; exit 1; fi fi echo "==> Build local, marqueur ${HASH}" rm -rf dist QUARTIER_BUILD_HASH="${HASH}" npm run build --silent test -f dist/index.html && test -f dist/build.json || { echo "ÉCHEC : dist incomplet"; exit 1; } POIDS="$(du -sk dist | cut -f1)" echo " dist : ${POIDS} Ko" echo "==> Transfert vers ${VPS_HOST}:${DEST}/releases/${HASH}" tar -C dist -czf "/tmp/quartier-${HASH}.tgz" . scp -q "/tmp/quartier-${HASH}.tgz" "${VPS_HOST}:/tmp/" rm -f "/tmp/quartier-${HASH}.tgz" ssh "${VPS_HOST}" bash -s -- "${HASH}" "${DEST}" "${DOMAINE}" "${GARDER}" <<'EOF' set -euo pipefail HASH="$1"; DEST="$2"; DOMAINE="$3"; GARDER="$4" REL="${DEST}/releases/${HASH}" rm -rf "${REL}"; mkdir -p "${REL}" tar -C "${REL}" -xzf "/tmp/quartier-${HASH}.tgz"; rm -f "/tmp/quartier-${HASH}.tgz" chmod -R a+rX "${REL}" PREC="$(readlink -f "${DEST}/current" 2>/dev/null || true)" ln -sfn "${REL}" "${DEST}/current" echo "==> Smoke local (fichier en place, puis Caddy en TLS local avec --resolve)" grep -q "\"hash\":\"${HASH}\"" "${DEST}/current/build.json" || { echo "ÉCHEC : ${DEST}/current/build.json ne porte pas ${HASH}" if [ -n "${PREC}" ]; then ln -sfn "${PREC}" "${DEST}/current"; echo " retour arrière sur ${PREC}"; fi exit 1 } # Caddy redirige http → https (308) : on interroge donc le vhost en TLS, en local, sans dépendre du DNS. # Sans certificat (DNS absent), la poignée de main TLS échoue : on l'accepte comme « route posée, certificat en attente ». CODE="$(curl -sk -m 10 --resolve "${DOMAINE}:443:127.0.0.1" -o /tmp/quartier-smoke.json -w '%{http_code}' "https://${DOMAINE}/build.json" 2>/dev/null || true)"; CODE="${CODE:-000}" if [ "${CODE}" = "200" ] && grep -q "\"hash\":\"${HASH}\"" /tmp/quartier-smoke.json; then echo " marqueur ${HASH} servi par Caddy (TLS local)" else HTTP="$(curl -s -m 10 -o /dev/null -w '%{http_code}' -H "Host: ${DOMAINE}" "http://127.0.0.1/build.json" || echo 000)" if [ "${HTTP}" = "308" ]; then echo " Caddy route bien ${DOMAINE} (308 → https) ; certificat pas encore émis (TLS local : ${CODE}). Fichier vérifié sur disque." else echo "ÉCHEC smoke : TLS local ${CODE}, http ${HTTP}" if [ -n "${PREC}" ]; then ln -sfn "${PREC}" "${DEST}/current"; echo " retour arrière sur ${PREC}"; fi exit 1 fi fi echo "==> Rotation : garde ${GARDER} releases" cd "${DEST}/releases" ls -1t | grep -v "^bootstrap$" | tail -n +$((GARDER + 1)) | while read -r r; do [ "$(readlink -f "${DEST}/current")" = "$(readlink -f "${r}")" ] || { rm -rf "${r}"; echo " supprimé ${r}"; } done EOF echo "==> Smoke public (informatif)" PUB="$(curl -s -m 10 -o /dev/null -w '%{http_code}' "https://${DOMAINE}/build.json" || true)" if [ "${PUB}" = "200" ]; then echo " https://${DOMAINE}/build.json → 200 : $(curl -s -m 10 "https://${DOMAINE}/build.json")" else echo " https://${DOMAINE} → ${PUB} (DNS ou certificat pas encore en place : item MOA Q-A)" fi echo "OK — ${HASH} en place."