[FIX] script forgejo: redémarrer le service quand la configuration change
Tout push finissait sur « Forgejo: Internal Server Error Decoding Failed », et le message ne désigne pas la cause. Le journal, lui, la donne : 403 sur /api/internal/hook/pre-receive, refusé par le contrôle du jeton interne. Le serveur comparait l'INTERNAL_TOKEN qu'il tenait EN MÉMOIRE à celui que le hook venait de lire sur le disque — deux valeurs différentes — et répondait 403 à son propre hook, que celui-ci ne sait pas décoder. La cause est ici : « systemctl enable --now » ne touche pas un service déjà actif. Le script redémarre donc quand le binaire, la configuration ou l'unité ont changé, et se tait sinon. Vérifié sur la VM : configuration régénérée service actif -> redémarrage -> push accepté ; relance sur forge saine -> aucun redémarrage, push toujours accepté. --- EN --- Every push ended on "Forgejo: Internal Server Error Decoding Failed", and the message does not name the cause. The log does: 403 on /api/internal/hook/pre-receive, refused by the internal token check. The server was comparing the INTERNAL_TOKEN it held IN MEMORY with the one the hook had just read from disk — two different values — and answered 403 to its own hook, which cannot decode a 403. The cause is here: "systemctl enable --now" does not touch an already active service. The script now restarts when the binary, the configuration or the unit changed, and stays quiet otherwise. Verified on the VM: config regenerated with the service active -> restart -> push accepted; replay on a healthy forge -> no restart, push still accepted. Assisted-by: Claude Opus 5
This commit is contained in:
parent
1785a2019d
commit
3ec7eb36f1
2 changed files with 69 additions and 5 deletions
|
|
@ -41,6 +41,11 @@ ADMIN_EMAIL="${FORGEJO_ADMIN_EMAIL:-admin@erplibre.local}"
|
|||
RUN_USER="${FORGEJO_USER:-git}"
|
||||
SKIP_ADMIN="${FORGEJO_SKIP_ADMIN:-0}"
|
||||
|
||||
# Ce qui a changé sur le disque pendant ce passage. Le service ne redémarre que
|
||||
# si quelque chose a bougé : rejouer le script sur une forge saine ne doit pas
|
||||
# l'interrompre, même deux secondes.
|
||||
CHANGED=0
|
||||
|
||||
BIN=/usr/local/bin/forgejo
|
||||
CONF_DIR=/etc/forgejo
|
||||
CONF="$CONF_DIR/app.ini"
|
||||
|
|
@ -152,6 +157,7 @@ else
|
|||
esac
|
||||
chmod +x "$src"
|
||||
sudo install -m 0755 "$src" "$BIN"
|
||||
CHANGED=1
|
||||
say "${Green}binaire posé : $BIN${Color_Off}"
|
||||
rm -rf "$tmp"
|
||||
trap - EXIT
|
||||
|
|
@ -249,11 +255,13 @@ LEVEL = info
|
|||
CONFEOF
|
||||
sudo chown root:"$RUN_USER" "$CONF"
|
||||
sudo chmod 640 "$CONF"
|
||||
CHANGED=1
|
||||
say "${Green}configuration écrite : $CONF${Color_Off}"
|
||||
fi
|
||||
|
||||
# --- 6. Service ------------------------------------------------------------
|
||||
sudo tee "$UNIT" >/dev/null <<UNITEOF
|
||||
unit_tmp=$(mktemp)
|
||||
cat > "$unit_tmp" <<UNITEOF
|
||||
[Unit]
|
||||
Description=Forgejo (Beyond coding. We forge.)
|
||||
After=network.target network-online.target
|
||||
|
|
@ -271,9 +279,29 @@ Environment=USER=$RUN_USER HOME=/home/$RUN_USER GITEA_WORK_DIR=$DATA
|
|||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNITEOF
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now forgejo.service >/dev/null 2>&1 \
|
||||
|| die "le service refuse de démarrer : sudo journalctl -u forgejo -n 40"
|
||||
if ! sudo cmp -s "$unit_tmp" "$UNIT" 2>/dev/null; then
|
||||
sudo install -m 0644 "$unit_tmp" "$UNIT"
|
||||
sudo systemctl daemon-reload
|
||||
CHANGED=1
|
||||
say "service défini : $UNIT"
|
||||
fi
|
||||
rm -f "$unit_tmp"
|
||||
|
||||
sudo systemctl enable forgejo.service >/dev/null 2>&1 || true
|
||||
# « restart » et NON « enable --now » quand quelque chose a changé : « --now »
|
||||
# ne touche pas à un service déjà actif, qui garde alors sa configuration en
|
||||
# MÉMOIRE. Vécu, et le symptôme ne désigne pas la cause : le serveur comparait
|
||||
# son ancien INTERNAL_TOKEN à celui que le hook venait de lire sur le disque, et
|
||||
# répondait 403 à son propre hook. Tout push finissait sur « Forgejo: Internal
|
||||
# Server Error Decoding Failed », le hook ne sachant pas décoder un 403.
|
||||
if [ "$CHANGED" = 1 ]; then
|
||||
sudo systemctl restart forgejo.service \
|
||||
|| die "le service refuse de démarrer : sudo journalctl -u forgejo -n 40"
|
||||
say "service redémarré (configuration ou binaire modifié)"
|
||||
elif ! systemctl is-active --quiet forgejo.service; then
|
||||
sudo systemctl start forgejo.service \
|
||||
|| die "le service refuse de démarrer : sudo journalctl -u forgejo -n 40"
|
||||
fi
|
||||
|
||||
# --- 7. Attendre qu'il RÉPONDE --------------------------------------------
|
||||
# Une requête HTTP, pas un « systemctl is-active » : le service est « active »
|
||||
|
|
@ -286,7 +314,10 @@ sudo systemctl enable --now forgejo.service >/dev/null 2>&1 \
|
|||
# silence, au premier tour de la boucle, code 1 sans un mot — vécu.
|
||||
ready=0
|
||||
for i in $(seq 1 60); do
|
||||
if curl -fsS -o /dev/null --max-time 3 \
|
||||
# « -fs » sans « -S » : dans une boucle de réessai, le message de curl est
|
||||
# du bruit — « Failed to connect » au premier tour est normal, le service
|
||||
# vient de redémarrer. C'est le die final qui parle si rien ne répond.
|
||||
if curl -fs -o /dev/null --max-time 3 \
|
||||
"http://127.0.0.1:$HTTP_PORT/api/v1/version"; then
|
||||
ready=1
|
||||
break
|
||||
|
|
|
|||
|
|
@ -339,6 +339,39 @@ class TestTheScriptGuards(unittest.TestCase):
|
|||
self.assertIn("FORGEJO_ADMIN_USER:-erplibre", self.body)
|
||||
self.assertNotIn("FORGEJO_ADMIN_USER:-admin}", self.body)
|
||||
|
||||
def test_it_restarts_when_something_changed(self):
|
||||
"""« enable --now » ne touche PAS un service déjà actif : il garde alors
|
||||
sa configuration en mémoire. Vécu, et le symptôme ne désignait pas la
|
||||
cause — le serveur comparait son ancien INTERNAL_TOKEN à celui que le
|
||||
hook venait de lire, répondait 403 à son propre hook, et tout push
|
||||
finissait sur « Internal Server Error Decoding Failed »."""
|
||||
self.assertIn("systemctl restart forgejo.service", self.body)
|
||||
self.assertNotIn("enable --now forgejo", self.body)
|
||||
|
||||
def test_the_restart_is_conditional(self):
|
||||
"""Rejouer le script sur une forge saine ne doit pas l'interrompre,
|
||||
même deux secondes."""
|
||||
self.assertIn("CHANGED=0", self.body)
|
||||
self.assertIn('[ "$CHANGED" = 1 ]', self.body)
|
||||
# Trois évènements le lèvent : binaire posé, config écrite, unité
|
||||
# modifiée.
|
||||
self.assertEqual(3, self.body.count("CHANGED=1"))
|
||||
|
||||
def test_the_unit_is_compared_before_being_written(self):
|
||||
"""Sans comparaison, l'unité serait réécrite à l'identique et le
|
||||
service redémarrerait pour rien à chaque passage."""
|
||||
self.assertIn("cmp -s", self.body)
|
||||
|
||||
def test_the_readiness_loop_stays_quiet_while_retrying(self):
|
||||
"""« Failed to connect » au premier tour est normal — le service vient
|
||||
de redémarrer. C'est le die final qui parle."""
|
||||
# La commande est coupée sur deux lignes : on regarde le BLOC de la
|
||||
# boucle, pas la ligne qui porte l'URL.
|
||||
start = self.body.index("ready=0")
|
||||
block = self.body[start : self.body.index('[ "$ready" = 1 ]', start)]
|
||||
self.assertIn("curl -fs -o /dev/null", block)
|
||||
self.assertNotIn("-fsS", block)
|
||||
|
||||
def test_it_touches_no_package_manager(self):
|
||||
"""C'est ce qui le rend portable : le binaire est statique."""
|
||||
for pm in ("apt-get install", "dnf install", "pacman -S", "zypper"):
|
||||
|
|
|
|||
Loading…
Reference in a new issue