Scaffold Vite + TypeScript + Three.js (scène vide : sol + boîte, marqueur de build), store et encodage de l'état dans l'URL (5 tests), lint de frontières entre modules, pipeline d'extraction Rhino (graine du 26/09) et première géométrie v0, deploy.sh statique (releases + lien symbolique + smoke), AGENTS.md, RUNBOOK, licences MIT + CC BY-SA 4.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
85 lines
4.1 KiB
Bash
Executable File
85 lines
4.1 KiB
Bash
Executable File
#!/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/<hash>/ → 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/<hash> /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" || echo 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."
|