diff --git a/deploy/HOSTINGER_GITHUB_SETUP.md b/deploy/HOSTINGER_GITHUB_SETUP.md index 3b30d50..925f3e2 100644 --- a/deploy/HOSTINGER_GITHUB_SETUP.md +++ b/deploy/HOSTINGER_GITHUB_SETUP.md @@ -20,6 +20,7 @@ Chaque push sur `main` lance d'abord la CI (`.github/workflows/ci.yml`). Si elle - `/etc/parenthese/build.env` : variables de build du front et de la landing (`VITE_POSTHOG_KEY`, `VITE_POSTHOG_HOST`) - `/etc/cron.d/parenthese-backup` : sauvegarde nocturne par `deploy/backup.sh` - `/etc/genealogy/backup.env` (facultatif) : rétention et envoi S3 des sauvegardes +- `/usr/local/sbin/parenthese-ssh-deploy` : copie de `deploy/ssh-deploy.sh`, commande forcée de la clé de déploiement (voir plus bas) ## Prérequis du serveur @@ -28,3 +29,19 @@ Node 20, `pm2` en global, PostgreSQL joignable par `DATABASE_URL`, nginx configu ## Déployer à la main `bash deploy/deploy.sh --sync` depuis le dépôt sur le serveur. `FORCE_FULL=1` pour tout reconstruire. Ne pas le lancer pendant un déploiement GitHub : le verrou le fera attendre. + +## Clé de déploiement restreinte + +La clé de GitHub ne doit pouvoir que déployer un commit de `main`, jamais lancer une autre commande sur le serveur. On la lie à `deploy/ssh-deploy.sh`, copié hors du dépôt parce que le déploiement réécrit le dépôt : + +``` +install -m 755 -o root -g root /var/www/parenthese/deploy/ssh-deploy.sh /usr/local/sbin/parenthese-ssh-deploy +``` + +Puis, dans `authorized_keys`, devant la ligne de la clé `github-actions-parenthese-public` : + +``` +command="/usr/local/sbin/parenthese-ssh-deploy",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAA… github-actions-parenthese-public +``` + +Le workflow n'a pas à changer. Après une mise à jour de `deploy/ssh-deploy.sh`, refaire l'`install`. diff --git a/deploy/ssh-deploy.sh b/deploy/ssh-deploy.sh new file mode 100755 index 0000000..42a2f70 --- /dev/null +++ b/deploy/ssh-deploy.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Commande forcée de la clé de déploiement GitHub, posée devant elle dans /root/.ssh/authorized_keys : +# command="/usr/local/sbin/parenthese-ssh-deploy",no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty ssh-ed25519 AAAA… github-actions-parenthese-public +# Avec elle, la clé ne peut plus que déployer un commit qui appartient déjà à main : une clé volée ne lance aucune +# autre commande. Le workflow « Deploy Hostinger » n'a pas à changer : il envoie +# FORCE_FULL=<0|1> DEPLOY_SHA= bash -s +# suivi d'un script sur l'entrée standard. Seuls FORCE_FULL et DEPLOY_SHA sont lus, le script envoyé est ignoré, +# et la suite reprend la même logique (verrou, commit de main, pas de retour en arrière, deploy.sh). +# Installé hors du dépôt, parce que le déploiement réécrit le dépôt pendant qu'il tourne : +# install -m 755 -o root -g root /var/www/parenthese/deploy/ssh-deploy.sh /usr/local/sbin/parenthese-ssh-deploy +set -euo pipefail + +if ! [[ "${SSH_ORIGINAL_COMMAND:-}" =~ ^FORCE_FULL=([01])\ DEPLOY_SHA=([0-9a-f]{40})\ bash\ -s$ ]]; then + echo "Commande refusée : cette clé ne sert qu'à déployer" >&2 + exit 1 +fi +FORCE_FULL="${BASH_REMATCH[1]}" +DEPLOY_SHA="${BASH_REMATCH[2]}" +# Le script envoyé par le workflow n'est pas exécuté +cat > /dev/null + +cd /var/www/parenthese +exec flock -w 900 /tmp/parenthese-deploy.lock env PARENTHESE_DEPLOY_LOCKED=1 FORCE_FULL="$FORCE_FULL" DEPLOY_SHA="$DEPLOY_SHA" bash -c ' + set -euo pipefail + git fetch -q origin main + # Le commit validé doit appartenir à main + if ! git merge-base --is-ancestor "$DEPLOY_SHA" origin/main; then + echo "Commit $DEPLOY_SHA absent de main : rien n est déployé" >&2 + exit 1 + fi + # Un déploiement plus récent est déjà en ligne : pas de retour en arrière + if [ "$(git rev-parse HEAD)" != "$DEPLOY_SHA" ] && git merge-base --is-ancestor "$DEPLOY_SHA" HEAD; then + echo "Version plus récente déjà déployée, $DEPLOY_SHA ignoré" + exit 0 + fi + git reset -q --hard "$DEPLOY_SHA" + bash deploy/deploy.sh +'