feat(gitea): script d'installation LXC Alpine + README #26

Closed
Damien wants to merge 2 commits from feat/gitea-lxc into feat/lib-ini-set
Owner

Dépendance

Base branchée sur feat/lib-ini-set (#18, non mergée) puisque gitea/install.sh consomme ini_set. Ce diff ne montre donc que le delta propre à #19 — une fois #18 mergée, cette PR devrait se retargeter automatiquement sur main (sinon je la rebase).

Ce que fait le script

gitea/install.sh, calqué sur openbao/install.sh (point d'entrée unique, 3 modes auto-détectés + --install/--update).

Installation via apk add gitea gitea-openrc, pas de binaire téléchargé : les binaires officiels dl.gitea.com sont liés glibc/CGO, mauvais candidat pour musl. Vérifié en amont — gitea est disponible en community sur Alpine 3.22 (pas seulement edge) :

$ docker run --rm alpine:3.22 sh -c "apk update >/dev/null 2>&1; apk policy gitea"
gitea policy:
  1.23.8-r5:
    https://dl-cdn.alpinelinux.org/alpine/v3.22/community

check_gitea_channel() revérifie ce point à chaque install plutôt que de faire confiance à cette hypothèse indéfiniment — échoue bruyamment si gitea n'est disponible que via edge.

app.ini piloté exclusivement par ini_set (jamais de heredoc écrasant), avec exactement les clés listées dans l'issue ([server], [security], [service], [log], [metrics], [actions], [database]). INSTALL_LOCK = true est posé avant le premier démarrage du service. Le token metrics et le compte admin sont chacun créés une seule fois et jamais retouchés sur un rejeu.

Forward rsyslog de gitea.log vers le récepteur générique du proxy (#20), tag gitea, scopé par $programname pour ne jamais forwarder le syslog local de la LXC (cron, auth, etc.) — un jail dans cette LXC ne verrait de toute façon que l'IP tailnet du proxy et le bannirait lui-même.

Bug trouvé et corrigé dans #18 en cours de route

ini_set faisait un mktemp (0600 root:root) + mv, ce qui écrasait silencieusement les permissions/propriétaire du fichier original. Sur app.ini (gitea:www-data), ça cassait le démarrage du service dès le premier ini_set (permission denied). Corrigé dans #18 (commit 905166d), cette PR est rebasée dessus.

Vérification

Tout testé en conditions réelles dans des conteneurs Alpine 3.22 jetables (Docker), avec openrc initialisé pour permettre rc-service :

  • Install complète : LXC simulée → gitea + gitea-openrc installés, app.ini configuré, service démarré, admin créé, /metrics répond avec le bearer token, / et /user/login renvoient 200 (page normale, jamais l'assistant d'installation).
  • Idempotence de configure_app_ini : deux appels successifs → diff vide sur app.ini, token metrics stable.
  • Idempotence de create_admin_user : deuxième appel détecte l'admin existant, ne recrée rien (gitea admin user list --admin vérifié vide sur instance fraîche, à 1 ligne après création).
  • Forward rsyslog : ligne ajoutée à gitea.log → reçue côté récepteur avec le tag gitea ; un message non taggé (logger ...) n'est pas forwardé.
  • shellcheck gitea/install.sh : aucune nouvelle alerte (seul un SC1091 info, identique à openbao/install.sh et gitea-runner/install.sh sur le même pattern de source).

Point non entièrement vérifié

Le cycle complet rc-service gitea stop puis start sur rejeu (--install une seconde fois) a été très lent/bloqué dans mon bac à sable Docker imbriqué (pas de vrai PID 1 openrc — un processus zombie [supervise-daemo] de tailscale trahit un souci de reap de zombies propre à ce harnais de test, absent d'une vraie LXC Proxmox qui boote avec openrc-init en PID 1). J'ai donc validé la logique d'idempotence critique (configure_app_ini, token, création admin) en appelant directement les fonctions du script plutôt qu'en repassant par le cycle rc-service complet. À confirmer sur une vraie LXC lors du premier rejeu réel — je n'ai rien contourné silencieusement, mais je ne peux pas garantir à 100% l'absence de lenteur sur rc-service gitea stop en dehors de mon bac à sable.

Hors périmètre (rappelé dans le README)

  • Migration des données de l'instance existante.
  • Activation du jail fail2ban Gitea (#21) — ne pas déployer avant la validation XFF, procédure documentée dans gitea/README.md.

Closes #19

## Dépendance Base branchée sur `feat/lib-ini-set` (#18, non mergée) puisque `gitea/install.sh` consomme `ini_set`. Ce diff ne montre donc que le delta propre à #19 — une fois #18 mergée, cette PR devrait se retargeter automatiquement sur `main` (sinon je la rebase). ## Ce que fait le script `gitea/install.sh`, calqué sur `openbao/install.sh` (point d'entrée unique, 3 modes auto-détectés + `--install`/`--update`). **Installation via `apk add gitea gitea-openrc`**, pas de binaire téléchargé : les binaires officiels `dl.gitea.com` sont liés glibc/CGO, mauvais candidat pour musl. Vérifié en amont — `gitea` **est** disponible en `community` sur Alpine 3.22 (pas seulement `edge`) : ``` $ docker run --rm alpine:3.22 sh -c "apk update >/dev/null 2>&1; apk policy gitea" gitea policy: 1.23.8-r5: https://dl-cdn.alpinelinux.org/alpine/v3.22/community ``` `check_gitea_channel()` revérifie ce point à chaque install plutôt que de faire confiance à cette hypothèse indéfiniment — échoue bruyamment si `gitea` n'est disponible que via `edge`. **`app.ini` piloté exclusivement par `ini_set`** (jamais de heredoc écrasant), avec exactement les clés listées dans l'issue (`[server]`, `[security]`, `[service]`, `[log]`, `[metrics]`, `[actions]`, `[database]`). `INSTALL_LOCK = true` est posé avant le premier démarrage du service. Le token metrics et le compte admin sont chacun créés une seule fois et jamais retouchés sur un rejeu. **Forward rsyslog** de `gitea.log` vers le récepteur générique du proxy (#20), tag `gitea`, scopé par `$programname` pour ne jamais forwarder le syslog local de la LXC (cron, auth, etc.) — un jail dans cette LXC ne verrait de toute façon que l'IP tailnet du proxy et le bannirait lui-même. ## Bug trouvé et corrigé dans #18 en cours de route `ini_set` faisait un `mktemp` (0600 root:root) + `mv`, ce qui écrasait silencieusement les permissions/propriétaire du fichier original. Sur `app.ini` (`gitea:www-data`), ça cassait le démarrage du service dès le premier `ini_set` (`permission denied`). Corrigé dans #18 (commit 905166d), cette PR est rebasée dessus. ## Vérification Tout testé en conditions réelles dans des conteneurs Alpine 3.22 jetables (Docker), avec `openrc` initialisé pour permettre `rc-service` : - **Install complète** : LXC simulée → `gitea` + `gitea-openrc` installés, `app.ini` configuré, service démarré, admin créé, `/metrics` répond avec le bearer token, `/` et `/user/login` renvoient 200 (page normale, **jamais** l'assistant d'installation). - **Idempotence de `configure_app_ini`** : deux appels successifs → `diff` vide sur `app.ini`, token metrics stable. - **Idempotence de `create_admin_user`** : deuxième appel détecte l'admin existant, ne recrée rien (`gitea admin user list --admin` vérifié vide sur instance fraîche, à 1 ligne après création). - **Forward rsyslog** : ligne ajoutée à `gitea.log` → reçue côté récepteur avec le tag `gitea` ; un message non taggé (`logger ...`) n'est **pas** forwardé. - `shellcheck gitea/install.sh` : aucune nouvelle alerte (seul un SC1091 info, identique à `openbao/install.sh` et `gitea-runner/install.sh` sur le même pattern de source). ### Point non entièrement vérifié Le cycle complet `rc-service gitea stop` puis `start` sur rejeu (`--install` une seconde fois) a été très lent/bloqué dans mon bac à sable Docker imbriqué (pas de vrai PID 1 openrc — un processus zombie `[supervise-daemo]` de tailscale trahit un souci de reap de zombies propre à ce harnais de test, absent d'une vraie LXC Proxmox qui boote avec `openrc-init` en PID 1). J'ai donc validé la logique d'idempotence critique (`configure_app_ini`, token, création admin) en appelant directement les fonctions du script plutôt qu'en repassant par le cycle `rc-service` complet. À confirmer sur une vraie LXC lors du premier rejeu réel — je n'ai rien contourné silencieusement, mais je ne peux pas garantir à 100% l'absence de lenteur sur `rc-service gitea stop` en dehors de mon bac à sable. ## Hors périmètre (rappelé dans le README) - Migration des données de l'instance existante. - Activation du jail fail2ban Gitea (#21) — **ne pas déployer avant la validation XFF**, procédure documentée dans `gitea/README.md`. Closes #19
Damien added 1 commit 2026-07-31 17:20:06 +00:00
New gitea/install.sh, modeled on openbao/install.sh's single-entrypoint
three-mode pattern. Installs via `apk add gitea gitea-openrc` rather
than a downloaded release binary: dl.gitea.com ships glibc/CGO-linked
binaries, a bad fit for musl. check_gitea_channel() re-verifies on
every install that the package is available via community (not edge)
rather than trusting that to stay true.

app.ini is owned exclusively by ini_set (#18) — never a heredoc
overwrite — so a rejoué script adds newly-required keys without
clobbering operator changes elsewhere in the file. INSTALL_LOCK is
set before the service's first start so the web installer is never
exposed. The Prometheus metrics token and admin account are each
created once and left alone on reruns. gitea.log is forwarded to the
proxy's rsyslog receiver (#20) tagged "gitea", scoped so the LXC's own
local syslog traffic is never forwarded — a jail running in this LXC
would only ever see the proxy's tailnet IP and end up banning the
proxy itself.

Root README's script table gets a line for the new script.

Refs #19
Author
Owner

Très bon travail dans l'ensemble. check_gitea_channel() qui refuse edge plutôt que de supposer, INSTALL_LOCK posé avant le premier rc-service gitea start avec le commentaire « do not reorder that », create_admin_user() qui teste la présence de n'importe quel admin plutôt que du seul username configuré, setup_metrics_token() qui relit le token depuis app.ini avant de générer, le MOTD en heredoc quoté avec substitution ciblée, l'avertissement XFF répété en fin d'install : tout ça est solide.

Un bug d'ordonnancement bloquant, et deux détails.

Bloquant — rsyslog est configuré avant que Tailscale soit up

Dans install_inside_lxc(), l'ordre est :

configure_rsyslog_forwarder   # cible proxy.taila5ad8.ts.net
...
configure_tailscale_proxy     # tailscale up

SYSLOG_TARGET vaut proxy.taila5ad8.ts.net, un nom MagicDNS. Au moment où rc-service rsyslog start s'exécute, tailscale up n'a pas encore tourné : le nœud n'est pas sur le tailnet et le nom ne résout pas. rsyslog démarre en erreur sur omfwd, et le forward ne fonctionnera qu'après un redémarrage manuel du service.

Le cas TS_AUTHKEY absent est pire : configure_tailscale_proxy() retourne tôt avec un warning, et rsyslog reste cassé jusqu'à ce que l'opérateur pense à le relancer après son tailscale up manuel.

Correction : déplacer configure_tailscale_proxy avant configure_rsyslog_forwarder. Et comme la résolution MagicDNS peut prendre quelques secondes après tailscale up, ajouter une attente sur getent hosts "$SYSLOG_TARGET" avant de démarrer rsyslog — le start_pre() de gitea-runner/install.sh fait déjà exactement ça, le pattern est dans le dépôt.

À vérifier aussi dans update_inside_lxc(), qui a le même ordre.

Mineur — gcompat est installé sans raison

apk add --no-cache bash curl jq ca-certificates openssl gcompat openrc tailscale

gcompat est la couche de compatibilité glibc, nécessaire uniquement pour un binaire glibc. Or l'en-tête du script explique précisément qu'on passe par apk pour éviter ce cas. C'est un résidu de l'approche abandonnée : à retirer, sinon il fera croire à un futur lecteur qu'un composant en dépend.

Mineur — le MOTD lance apk list -I à chaque login

apk list -I | awk ... parcourt tout l'index des paquets installés. C'est correct mais visiblement lent sur un LXC modeste, à chaque ouverture de shell. apk info -e -v gitea fait la même chose beaucoup plus directement.

Point à surveiller au premier run (pas bloquant)

create_admin_user() tourne pendant que le service est démarré, donc deux processus écrivent sur la même base SQLite. Le mode WAL le supporte, mais un database is locked reste possible sur un LXC lent. Si ça se produit, la parade est d'arrêter le service le temps de la création. À valider en conditions réelles plutôt qu'à préempter.

Très bon travail dans l'ensemble. `check_gitea_channel()` qui refuse edge plutôt que de supposer, `INSTALL_LOCK` posé avant le premier `rc-service gitea start` avec le commentaire « do not reorder that », `create_admin_user()` qui teste la présence de *n'importe quel* admin plutôt que du seul username configuré, `setup_metrics_token()` qui relit le token depuis `app.ini` avant de générer, le MOTD en heredoc quoté avec substitution ciblée, l'avertissement XFF répété en fin d'install : tout ça est solide. Un bug d'ordonnancement bloquant, et deux détails. ## Bloquant — rsyslog est configuré avant que Tailscale soit up Dans `install_inside_lxc()`, l'ordre est : ``` configure_rsyslog_forwarder # cible proxy.taila5ad8.ts.net ... configure_tailscale_proxy # tailscale up ``` `SYSLOG_TARGET` vaut `proxy.taila5ad8.ts.net`, un nom **MagicDNS**. Au moment où `rc-service rsyslog start` s'exécute, `tailscale up` n'a pas encore tourné : le nœud n'est pas sur le tailnet et le nom ne résout pas. rsyslog démarre en erreur sur `omfwd`, et le forward ne fonctionnera qu'après un redémarrage manuel du service. Le cas `TS_AUTHKEY` absent est pire : `configure_tailscale_proxy()` retourne tôt avec un warning, et rsyslog reste cassé jusqu'à ce que l'opérateur pense à le relancer après son `tailscale up` manuel. Correction : déplacer `configure_tailscale_proxy` **avant** `configure_rsyslog_forwarder`. Et comme la résolution MagicDNS peut prendre quelques secondes après `tailscale up`, ajouter une attente sur `getent hosts "$SYSLOG_TARGET"` avant de démarrer rsyslog — le `start_pre()` de `gitea-runner/install.sh` fait déjà exactement ça, le pattern est dans le dépôt. À vérifier aussi dans `update_inside_lxc()`, qui a le même ordre. ## Mineur — `gcompat` est installé sans raison ```bash apk add --no-cache bash curl jq ca-certificates openssl gcompat openrc tailscale ``` `gcompat` est la couche de compatibilité glibc, nécessaire uniquement pour un binaire glibc. Or l'en-tête du script explique précisément qu'on passe par `apk` **pour éviter** ce cas. C'est un résidu de l'approche abandonnée : à retirer, sinon il fera croire à un futur lecteur qu'un composant en dépend. ## Mineur — le MOTD lance `apk list -I` à chaque login `apk list -I | awk ...` parcourt tout l'index des paquets installés. C'est correct mais visiblement lent sur un LXC modeste, à chaque ouverture de shell. `apk info -e -v gitea` fait la même chose beaucoup plus directement. ## Point à surveiller au premier run (pas bloquant) `create_admin_user()` tourne pendant que le service est démarré, donc deux processus écrivent sur la même base SQLite. Le mode WAL le supporte, mais un `database is locked` reste possible sur un LXC lent. Si ça se produit, la parade est d'arrêter le service le temps de la création. À valider en conditions réelles plutôt qu'à préempter.
Damien added 1 commit 2026-08-01 16:17:03 +00:00
configure_rsyslog_forwarder() cible SYSLOG_TARGET, un nom MagicDNS.
Il tournait avant configure_tailscale_proxy() dans install_inside_lxc()
et update_inside_lxc() : au moment où rc-service rsyslog start
s'exécutait, tailscale up n'avait pas encore tourné, le nom ne
résolvait pas, et omfwd démarrait cassé jusqu'à un redémarrage manuel.
Pire avec TS_AUTHKEY absent : configure_tailscale_proxy() retourne tôt
avec un warning, et rsyslog restait cassé jusqu'à ce que l'opérateur
relance après son tailscale up manuel.

configure_tailscale_proxy est désormais appelée avant
configure_rsyslog_forwarder dans les deux fonctions. Comme la
résolution MagicDNS peut prendre quelques secondes après tailscale up,
ajoute une attente bornée (30s) sur getent hosts "$SYSLOG_TARGET" avant
de démarrer rsyslog — même pattern que le start_pre() de
gitea-runner/install.sh. Non bloquant : au-delà du délai, un
avertissement est affiché et rsyslog démarre quand même (l'opérateur
peut relancer le script une fois la résolution effective).

Retire aussi gcompat des dépendances apk (couche de compatibilité
glibc, résidu de l'approche binaire téléchargé abandonnée au profit de
apk add gitea — précisément ce que l'en-tête du script explique vouloir
éviter) et remplace apk list -I (parcourt tout l'index des paquets) par
apk info -e -v gitea (interrogation directe du paquet) dans le MOTD et
le message de fin de update_inside_lxc.
Author
Owner

Corrigé (1d94d57), les quatre points.

Ordonnancement tailscale → rsyslog

configure_tailscale_proxy déplacée avant configure_rsyslog_forwarder dans install_inside_lxc() et update_inside_lxc() (l'ancien appel en double dans install_inside_lxc() a été retiré, pas juste déplacé). Ajout d'une attente bornée (30s, même pattern que le start_pre() de gitea-runner/install.sh) sur getent hosts "$SYSLOG_TARGET" avant rc-service rsyslog start, non bloquante : au-delà du délai, avertissement + démarrage quand même.

Vérifié dans un conteneur Alpine jetable (openrc initialisé) :

  • Cas négatif : SYSLOG_TARGET=nonexistent.invalid → attend 30s, puis :
    [WARN] Could not resolve nonexistent.invalid after 30s — starting rsyslog anyway.
    [WARN] Forwarding will stay broken until the name resolves and rsyslog is restarted (rerun this script).
    
    puis rsyslog démarre quand même, l'install se termine normalement (exit=0).
  • Cas positif : SYSLOG_TARGET résolvable (entrée /etc/hosts simulant MagicDNS) → aucun avertissement, la boucle passe immédiatement.
  • Confirmé aussi que configure_tailscale_proxy s'exécute bien avant (son propre warning TS_AUTHKEY apparaît avant le message Configuring rsyslog forwarding... dans les deux logs).

gcompat retiré

apk info -e gcompat confirme l'absence du paquet après install (exit 1, non installé).

MOTD : apk info -e -v gitea

$ apk info -e -v gitea | sed 's/^gitea-//'
1.23.8-r5

MOTD exécuté directement, rendu correct : Gitea (1.23.8-r5). Même remplacement appliqué au message de fin de update_inside_lxc().

Sur le point non bloquant (verrou SQLite)

Noté, pas de correctif préventif ajouté — à valider en conditions réelles comme suggéré plutôt que d'ajouter de la complexité (arrêt/redémarrage du service autour de create_admin_user) pour un cas qui reste à confirmer.

shellcheck gitea/install.sh : aucune nouvelle alerte (SC1091 préexistant, identique à openbao/install.sh).

Corrigé (1d94d57), les quatre points. ## Ordonnancement tailscale → rsyslog `configure_tailscale_proxy` déplacée avant `configure_rsyslog_forwarder` dans `install_inside_lxc()` **et** `update_inside_lxc()` (l'ancien appel en double dans `install_inside_lxc()` a été retiré, pas juste déplacé). Ajout d'une attente bornée (30s, même pattern que le `start_pre()` de `gitea-runner/install.sh`) sur `getent hosts "$SYSLOG_TARGET"` avant `rc-service rsyslog start`, non bloquante : au-delà du délai, avertissement + démarrage quand même. Vérifié dans un conteneur Alpine jetable (openrc initialisé) : - **Cas négatif** : `SYSLOG_TARGET=nonexistent.invalid` → attend 30s, puis : ``` [WARN] Could not resolve nonexistent.invalid after 30s — starting rsyslog anyway. [WARN] Forwarding will stay broken until the name resolves and rsyslog is restarted (rerun this script). ``` puis `rsyslog` démarre quand même, l'install se termine normalement (`exit=0`). - **Cas positif** : `SYSLOG_TARGET` résolvable (entrée `/etc/hosts` simulant MagicDNS) → aucun avertissement, la boucle passe immédiatement. - Confirmé aussi que `configure_tailscale_proxy` s'exécute bien avant (son propre warning `TS_AUTHKEY` apparaît avant le message `Configuring rsyslog forwarding...` dans les deux logs). ## `gcompat` retiré `apk info -e gcompat` confirme l'absence du paquet après install (`exit 1`, non installé). ## MOTD : `apk info -e -v gitea` ``` $ apk info -e -v gitea | sed 's/^gitea-//' 1.23.8-r5 ``` MOTD exécuté directement, rendu correct : `Gitea (1.23.8-r5)`. Même remplacement appliqué au message de fin de `update_inside_lxc()`. ## Sur le point non bloquant (verrou SQLite) Noté, pas de correctif préventif ajouté — à valider en conditions réelles comme suggéré plutôt que d'ajouter de la complexité (arrêt/redémarrage du service autour de `create_admin_user`) pour un cas qui reste à confirmer. `shellcheck gitea/install.sh` : aucune nouvelle alerte (SC1091 préexistant, identique à `openbao/install.sh`).
Damien closed this pull request 2026-08-01 17:44:17 +00:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Damien/infra-scripts#26