Pour entretenir une instance Forgejo 15 LTS, on pilote ses tâches planifiées
(les travaux de fond qu'elle lance seule) par l'API et par des variables
d'environnement, on la diagnostique avec
forgejo doctor check, on mesure où grossit le disque et on plafonne les
utilisateurs par des quotas. Aucune de ces opérations n'a de commande unique :
les tâches se lisent dans l'API, doctor se lit à la main, les notices dans la
base. La leçon s'adresse à l'administrateur dont la forge tourne déjà et qui
veut savoir quoi regarder chaque semaine, et quoi faire quand une ligne passe
au rouge.
Vous repartez avec cinq preuves : une tâche de fond lancée à la demande
dont on a mesuré l'effet, un planning réglé par un fichier de surcharge, un
défaut réparé par doctor check --fix, un quota qui refuse un push, et
une notice d'échec lue dans la base. Les commandes ont été exécutées avec
Forgejo 15.0.10 sur Ubuntu 24.04 (Docker 29.9.1, Compose v5.6.0, PostgreSQL
17.11), avec exactement la pile de la leçon
Installer Forgejo LTS, sans écart.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Consulter les tâches planifiées de Forgejo par son API, et en déclencher une à la demande.
- Régler le planning d'une tâche par un fichier de surcharge, et connaître le piège du retrait d'une variable.
- Mesurer l'effet du
git gcet du nettoyage des archives. - Diagnostiquer et réparer l'instance avec
forgejo doctor check --runet--fix. - Plafonner l'espace d'un utilisateur par un quota, et comprendre quand le refus tombe.
- Repérer où grossit le disque et retrouver l'échec d'une tâche dans les notices.
Quels sont les prérequis ?
Section intitulée « Quels sont les prérequis ? »Il vous faut la forge des leçons précédentes : Forgejo 15 LTS derrière
Caddy dans /opt/forgejo-lab, le nom forge.lab.test et le compte
fjadmin
(installation). Elle suppose
aussi la variable COMPOSE_FILE du .env, qui liste les fichiers Compose de
surcharge, et les variables FORGEJO__section__CLE, toutes deux expliquées
dans
Configuration initiale.
La sauvegarde qui précède toute réparation est celle de
Sauvegarder et restaurer Forgejo.
doctor check --list et --all sont déjà présentés dans
Assister les utilisateurs :
cette leçon va plus loin (--run, --fix, code de sortie) sans les
redire. Le jeton d'accès se révoque comme dans cette leçon-là, ses portées
sont celles de
Sécuriser l'instance, et
la montée de version est celle de
Mettre à jour Forgejo.
Vous devez savoir lire un code HTTP avec curl, écrire un fichier en
root avec sudo et lire du JSON. Préparez le shell de la VM : la
fonction F exécute forgejo dans le conteneur sous le compte git, un
jeton d'administrateur est écrit dans un fichier lisible de vous seul, et
api appelle l'API avec ce jeton. Le jeton de portée all ne convient qu'à un
lab jetable, et l'option -k de curl ignore le certificat de la CA du lab.
cd /opt/forgejo-labF() { sudo docker exec -u git forgejo forgejo "$@"; }(umask 077; F admin user generate-access-token --username fjadmin --token-name l10 --scopes all --raw > ~/.tok)api() { local m=$1 p=$2; shift 2; curl -sk -X "$m" -H "Authorization: token $(cat ~/.tok)" -H 'Content-Type: application/json' "$@" "https://forge.lab.test/api/v1$p"; }apic() { local m=$1 p=$2; shift 2; curl -sk -w '\nHTTP=%{http_code}\n' -X "$m" -H "Authorization: token $(cat ~/.tok)" -H 'Content-Type: application/json' "$@" "https://forge.lab.test/api/v1$p"; }echo "jeton : $(wc -c < ~/.tok) octets"La dernière ligne affiche jeton : 41 octets. Le fichier ~/.tok contient un
secret : ne l'affichez jamais, et détruisez-le en fin de leçon (section
« Comment défaire cette configuration ? »). api rend le corps de la réponse,
apic ajoute la ligne HTTP=<code> : on s'en sert quand c'est le code qui
compte.
Quels sont les leviers de maintenance de Forgejo ?
Section intitulée « Quels sont les leviers de maintenance de Forgejo ? »Forgejo s'entretient par cinq leviers, chacun avec son outil : les tâches
planifiées (API), forgejo doctor (CLI), les quotas (API), le disque (du et
psql) et les notices (table notice). Une notice est un message que
Forgejo écrit dans sa base quand une tâche de fond se termine ou échoue. Le
tableau dit où chercher selon la question. Il sert de plan de la leçon : chaque
ligne a sa section, et chaque section s'ouvre sur sa réponse en une phrase.
| Question | Levier | Section |
|---|---|---|
| Que fait la forge toute seule, et comment lancer ou replanifier une tâche ? | tâches planifiées, GET /api/v1/admin/cron, POST sur la tâche, variables FORGEJO__cron.… | « Quelles tâches de fond Forgejo lance-t-il seul ? » puis « Comment régler le planning d'une tâche ? » |
| Des fichiers de l'instance sont-ils faux ? | forgejo doctor check --run … --fix | « Comment diagnostiquer et réparer avec doctor check ? » |
| Un utilisateur prend-il trop de place ? | règles et groupes de quota | « Comment plafonner un utilisateur par un quota ? » |
| Où grossit le disque ? | du par dossier, psql | « Où grossit la forge et comment le mesurer ? » |
| Une tâche a-t-elle échoué ? | table notice, journal du conteneur | « Comment savoir qu'une tâche planifiée a échoué ? » |
Une instance saine n'a besoin d'aucune intervention : ces leviers servent à vérifier sans surprise, puis à réparer quand une vérification échoue. Pour qu'une alerte parte sans que vous lisiez ces sorties à la main, c'est la supervision qu'il faut brancher, celle de la formation Observabilité ; cette leçon donne ce qu'elle doit lire.
Quelles tâches de fond Forgejo lance-t-il seul ?
Section intitulée « Quelles tâches de fond Forgejo lance-t-il seul ? »Forgejo planifie 30 tâches de fond, listées par GET /api/v1/admin/cron avec
leur planning, leur nombre d'exécutions et leur prochaine échéance ; treize
n'ont aucun planning au départ. Une tâche planifiée (on dit aussi une
tâche cron, du nom du planificateur d'Unix) est un travail que Forgejo
lance seul à intervalle fixe : nettoyer, vérifier, compacter. Son planning
s'écrit @every 10m (toutes les dix minutes), @midnight ou @daily (une
fois par jour). Il n'existe pas de sous-commande pour les voir : forgejo admin --help n'offre que user, repo-sync-releases,
regenerate, auth et sendmail. Sur l'instance neuve, app.ini ne contient
d'ailleurs aucune section [cron…] : tout vient des valeurs par défaut.
api GET /admin/cron | python3 -c 'import sys,jsonfor t in json.load(sys.stdin): print(t["name"], "|", t["schedule"], "|", t["exec_times"], "|", t["next"][:19])'Chaque ligne montre le nom, le planning, le nombre d'exécutions depuis le démarrage et la prochaine échéance. Voici un extrait de notre sortie.
update_mirrors | @every 10m | 0 | 2026-10-16T10:24:55repo_health_check | @midnight | 0 | 2026-10-17T00:00:00archive_cleanup | @midnight | 1 | 2026-10-17T00:00:00delete_repo_archives | - | 0 | 0001-01-01T00:00:00git_gc_repos | - | 0 | 0001-01-01T00:00:00delete_old_actions | - | 0 | 0001-01-01T00:00:00update_checker | @every 168h | 0 | 2026-10-23T10:14:55stop_zombie_tasks | @every 5m | 1 | 2026-10-16T10:19:55cleanup_offline_runners | - | 0 | 0001-01-01T00:00:00Un planning - et une échéance au 0001-01-01 signalent une tâche
désactivée par défaut. Parmi elles, git_gc_repos, delete_repo_archives,
delete_inactive_accounts, delete_old_actions, cleanup_offline_runners et
delete_old_system_notices. Les tâches actives par défaut couvrent le
quotidien : nettoyage des archives (archive_cleanup), des branches
supprimées, des paquets, des tâches Actions bloquées (stop_zombie_tasks), et
une vérification de version toutes les 168 heures (update_checker). Le
piège : tant qu'on n'active pas git_gc_repos, cette tâche ne tourne
jamais, et aucun message ne le signale.
Comment lancer une tâche à la demande et mesurer son effet ?
Section intitulée « Comment lancer une tâche à la demande et mesurer son effet ? »POST /api/v1/admin/cron/<tâche> lance la tâche immédiatement et répond
204, même si elle est désactivée ; le compteur exec_times passe alors à
1. C'est le geste pour vérifier un effet sans attendre l'échéance, ou pour
purger tout de suite. La fonction cron_line affiche l'état d'une tâche avant
et après.
cron_line() { api GET /admin/cron | python3 -c 'import sys,jsonfor t in json.load(sys.stdin): if t["name"]=="'"$1"'": print(t["name"],"| schedule:",t["schedule"],"| exec_times:",t["exec_times"],"| prev:",t["prev"][:19])'; }cron_line git_gc_reposapic POST /admin/cron/git_gc_repos | tail -2sleep 8cron_line git_gc_reposAvant : git_gc_repos | schedule: - | exec_times: 0 | prev: 0001-01-01T00:00:00.
La requête répond HTTP=204. Après huit secondes : exec_times: 1 | prev: 2026-10-16T10:16:15. La tâche désactivée s'est exécutée une fois, sans
rien changer à son planning. Le code 204 dit que la demande est acceptée,
pas que la tâche a réussi : l'effet se mesure sur le disque, et l'échec se lit
dans les notices (section « Comment savoir qu'une tâche planifiée a échoué ? »).
Que font le gc et le nettoyage des archives ?
Section intitulée « Que font le gc et le nettoyage des archives ? »git_gc_repos compacte les objets de chaque dépôt : sur un dépôt de 40
pushes, les 123 objets libres sont passés dans un seul pack et le dossier est
passé de 1872 à 1024 Kio. Git range chaque version d'un fichier dans un
objet, d'abord isolé (« libre »). git gc (garbage collection, le
ménage de Git) regroupe les objets libres dans un seul fichier compressé, un
pack, et supprime ce qui ne sert plus. Pour le montrer, il faut un dépôt
qui a de l'historique. Le bloc crée gc-demo2, clone avec la CA de Caddy et pousse 40
fichiers de 20 Ko. L'en-tête d'authentification porte le jeton, d'où
credential.helper= vide et GIT_TERMINAL_PROMPT=0.
W=$(mktemp -d); cd "$W"sudo docker cp forgejo-proxy:/data/caddy/pki/authorities/local/root.crt "$W/root.crt"; sudo chown "$USER" "$W/root.crt"export GIT_SSL_CAINFO="$W/root.crt" GIT_TERMINAL_PROMPT=0H=(-c credential.helper= -c "http.extraHeader=Authorization: token $(cat ~/.tok)")CO() { sudo docker exec -u git forgejo git -C /data/git/repositories/fjadmin/gc-demo2.git count-objects -v | tr '\n' ' '; echo; }apic POST /user/repos -d '{"name":"gc-demo2","auto_init":true,"default_branch":"main"}' | tail -1git "${H[@]}" clone -q https://forge.lab.test/fjadmin/gc-demo2.git r && cd rgit config user.email a@example.test; git config user.name afor i in $(seq 1 40); do head -c 20000 /dev/urandom | base64 > "f$i.txt"; git add .; git commit -qm "c$i"; git "${H[@]}" push -q origin main; doneecho "AVANT :"; COsudo docker exec forgejo du -sk /data/git/repositories/fjadmin/gc-demo2.gitapic POST /admin/cron/git_gc_repos | tail -1sleep 8echo "APRES :"; COsudo docker exec forgejo du -sk /data/git/repositories/fjadmin/gc-demo2.gitcd "$W"La création répond HTTP=201. La mesure avant est count: 123 size: 1292 in-pack: 0 packs: 0 size-pack: 0 et 1872 Kio ; la mesure après est
count: 0 size: 0 in-pack: 123 packs: 1 size-pack: 815 et 1024 Kio. Les
objets libres (count) sont tous passés dans un pack (in-pack).
Le piège est dans CO. Lancé en root (sans -u git), git count-objects
échoue avec fatal: detected dubious ownership in repository, parce que le
dépôt appartient à git : c'est un garde-fou de Git, pas une panne. Nous avons
obtenu cette erreur à notre premier essai. Seul du répondait, d'où les deux
mesures.
Les archives (les .zip générés quand on télécharge une branche)
obéissent à deux tâches. Sur un dépôt gc-demo, créé comme gc-demo2
(sans les 40 pushes), nous avons téléchargé main.zip puis lancé
delete_repo_archives. Sans ce dépôt, le téléchargement répond 404.
apic POST /user/repos -d '{"name":"gc-demo","auto_init":true,"default_branch":"main"}' | tail -1curl -sk -o /dev/null -w 'archive http=%{http_code}\n' -H "Authorization: token $(cat ~/.tok)" https://forge.lab.test/api/v1/repos/fjadmin/gc-demo/archive/main.zipsudo docker exec forgejo sh -c 'find /data/gitea/repo-archive -type f; du -sk /data/gitea/repo-archive'apic POST /admin/cron/delete_repo_archives | tail -2sleep 4sudo docker exec forgejo sh -c 'find /data/gitea/repo-archive -type f; du -sk /data/gitea/repo-archive'La création répond HTTP=201, le téléchargement archive http=200, la
première mesure montre un .zip et 28 Kio, la seconde plus aucun fichier
et 24 Kio (les sous-dossiers vides restent). Cette tâche supprime toutes les
archives ; archive_cleanup, planifiée à minuit, est celle du quotidien : la
documentation lui donne OLDER_THAN = 24h et elle ne supprime donc que les
archives plus vieilles qu'un jour. Nous ne l'avons pas lancée. Les autres tâches de nettoyage (repo_health_check,
cleanup_actions, delete_old_actions, delete_inactive_accounts,
delete_old_system_notices) ont répondu 204 et incrémenté exec_times, sans
effet mesurable : l'instance était vide, il n'y avait rien à nettoyer.
Comment régler le planning d'une tâche ?
Section intitulée « Comment régler le planning d'une tâche ? »On pose des variables FORGEJO__cron.<tâche>__ENABLED, __SCHEDULE,
__NOTICE_ON_SUCCESS ou __OLDER_THAN dans un fichier Compose de surcharge
ajouté à COMPOSE_FILE, puis on recrée le conteneur. Le point dans le nom de
la section (cron.git_gc_repos) fonctionne tel quel. Le fichier suivant active
le gc toutes les deux minutes (un rythme de démonstration, pas de production),
demande une notice à chaque succès, ramène le nettoyage des archives à toutes
les 30 minutes (au lieu de minuit) et active la suppression des comptes jamais
activés.
sudo tee compose.maintenance.yaml >/dev/null <<'YAML'services: server: environment: FORGEJO__cron.git_gc_repos__ENABLED: "true" FORGEJO__cron.git_gc_repos__SCHEDULE: "@every 2m" FORGEJO__cron.git_gc_repos__NOTICE_ON_SUCCESS: "true" FORGEJO__cron.archive_cleanup__SCHEDULE: "@every 30m" FORGEJO__cron.delete_inactive_accounts__ENABLED: "true" FORGEJO__cron.delete_inactive_accounts__SCHEDULE: "@daily" FORGEJO__cron.delete_inactive_accounts__OLDER_THAN: "168h" FORGEJO__quota__ENABLED: "true"YAMLsudo grep -q '^COMPOSE_FILE=' .env || echo 'COMPOSE_FILE=compose.yaml:compose.maintenance.yaml' | sudo tee -a .env >/dev/nullsudo docker compose up -d 2>&1 | tail -4La dernière variable, FORGEJO__quota__ENABLED, sert à la section sur les
quotas. Sur notre installation neuve, COMPOSE_FILE n'existait pas : la
deuxième commande l'a créée, et docker compose config --services a listé
db, server et proxy. Sur une pile qui porte déjà COMPOSE_FILE, on
ajoute le fichier à la liste comme dans la leçon de sécurité, avec sudo sed -i 's#^COMPOSE_FILE=.*#&:compose.maintenance.yaml#' .env, ce que nous
n'avons pas rejoué sur cette installation. Le .env appartient à root en mode 600 :
c'est pourquoi le test se fait avec sudo grep, sans quoi grep échoue
sur « Permission denied » et la ligne serait ajoutée en double. Lancez toujours
docker compose depuis /opt/forgejo-lab, où se trouve le .env.
Vérifiez d'abord ce que Forgejo a écrit dans app.ini, puis attendez un tick
du planning.
sudo docker exec forgejo sh -c 'sed -n "/^\[cron\./,/^$/p;/^\[quota\]/,/^$/p" /data/gitea/conf/app.ini'sleep 150cron_line git_gc_reposLe fichier contient les sections [cron.git_gc_repos] (ENABLED = true,
NOTICE_ON_SUCCESS = true, SCHEDULE = @every 2m), [cron.delete_inactive_accounts]
(OLDER_THAN = 168h), [cron.archive_cleanup] et [quota] (ENABLED = true). Après 150 secondes, git_gc_repos affiche schedule: @every 2m | exec_times: 1 avec une prochaine échéance : la tâche s'est lancée seule,
sans POST. Le fichier de surcharge est donc bien pris en compte, sans
aucune édition à la main d'app.ini.
Que devient une clé quand on retire sa variable ?
Section intitulée « Que devient une clé quand on retire sa variable ? »Retirer la variable du fichier de surcharge ne retire pas la clé de
app.ini : Forgejo garde la dernière valeur écrite. C'est le piège classique
des variables FORGEJO__, déjà vu dans la leçon de sécurité. Nous l'avons
éprouvé en supprimant la ligne archive_cleanup__SCHEDULE du fichier, en
recréant le conteneur, puis en lisant app.ini.
grep -c 'archive_cleanup__SCHEDULE' compose.maintenance.yamlsudo sed -i '/archive_cleanup__SCHEDULE/d' compose.maintenance.yamlgrep -c 'archive_cleanup__SCHEDULE' compose.maintenance.yamlsudo docker compose up -d 2>&1 | grep -E "Recreate|Started" | head -3sudo docker exec forgejo env | grep -c 'archive_cleanup__SCHEDULE'sudo docker exec forgejo sh -c 'grep -A1 "^\[cron.archive_cleanup\]" /data/gitea/conf/app.ini'Le premier compteur vaut 1 (la ligne est encore là), les trois suivants
valent 0 : la variable n'est plus ni dans le fichier ni
dans le conteneur recréé (Container forgejo Recreate). Pourtant app.ini
affiche toujours [cron.archive_cleanup] et SCHEDULE = @every 30m.
Pour revenir à la valeur de départ, il faut donc la fixer explicitement :
le défaut relevé plus haut dans la liste est @midnight, mais nous n'avons pas
rejoué cette correction. Un premier essai, lancé depuis un dossier temporaire,
n'avait recréé aucun conteneur : la commande se lance depuis le dossier du
projet.
Comment diagnostiquer et réparer avec doctor check ?
Section intitulée « Comment diagnostiquer et réparer avec doctor check ? »forgejo doctor check lance des vérifications nommées ; --run <nom> en
choisit une, --fix répare ce qui peut l'être, et le code de sortie vaut 0
même quand la vérification affiche ERROR. Le dernier point est le piège de
la leçon : une supervision ne peut pas se fier au code. La liste des
vérifications (--list) est celle de la leçon sur les comptes ; on la relit ici
pour choisir un nom, puis on lit les options avec --help.
F doctor check --listF doctor check --help | sed -n '1,40p'Les 28 vérifications de la liste comptent 5 par défaut (marquées d'une
étoile) : paths, check-db-version, check-user-type, authorized-keys et
synchronize-repo-heads. Les options utiles ici sont --run (une
vérification par son nom, répétable), --default, --all, --fix
(« Automatically fix what we can »), --log-file (- envoie le journal
détaillé sur la sortie standard) et --color. Le nom doit être exact :
F doctor check --run check-hooks répond Command error: unknown checks: "check-hooks" avec le code 1. La vérification des hooks s'appelle hooks.
Comment réparer authorized_keys ?
Section intitulée « Comment réparer authorized_keys ? »Le fichier authorized_keys de l'utilisateur git est reconstruit à partir
de la base : si on le vide, doctor affiche [E] puis ERROR, et --fix le
régénère. Ce fichier dit à OpenSSH quelles clés publiques ont le droit de se
connecter au compte git, donc de pousser par SSH. Pour le montrer, ajoutons une clé SSH à fjadmin par l'API (la
base la connaît, donc le fichier doit la contenir), vérifions que tout va bien,
puis vidons le fichier.
K=$(mktemp -d); ssh-keygen -q -t ed25519 -N '' -f "$K/k"apic POST /user/keys -d "{\"title\":\"doc\",\"key\":\"$(cat $K/k.pub)\"}" | tail -1sudo docker exec forgejo sh -c 'grep -c "" /data/git/.ssh/authorized_keys'F doctor check --run authorized-keys; echo "rc=$?"sudo docker exec forgejo sh -c ': > /data/git/.ssh/authorized_keys'F doctor check --run authorized-keys 2>&1; echo "rc=$?"L'ajout répond HTTP=201 et le fichier compte 2 lignes. Le premier passage
affiche OK et rc=0. Après le vidage, la sortie devient :
[1] Check if OpenSSH authorized_keys file is up-to-date - [I] AppPath set to '/usr/local/bin/gitea' (was '/usr/local/bin/forgejo') - [E] Key in database is not present in /data/git/.ssh/authorized_keys: command="/usr/local/bin/gitea --config=/data/gitea/conf/app.ini serv key-2",… - [E] authorized_keys file "/data/git/.ssh/authorized_keys" contains validity errors.Regenerate it with: "forgejo admin regenerate keys"or "forgejo doctor check --run authorized-keys --fix"ERROR
All done (checks: 1).rc=0Lisez-la dans l'ordre : la ligne [E] donne le défaut (une clé de la base
absente du fichier), la ligne ERROR conclut, et la sortie propose deux
réparations. Le rc=0 est le piège : malgré ERROR, le code de sortie
n'est pas 1. Une supervision doit donc lire le texte (la présence de [E]
ou de ERROR), pas le code. La ligne [I] AppPath set to '/usr/local/bin/gitea' est normale : elle apparaît même quand tout va bien
et ne signale pas une erreur de configuration.
F doctor check --run authorized-keys --fix; echo "rc=$?"sudo docker exec forgejo sh -c 'grep -c "" /data/git/.ssh/authorized_keys'F doctor check --run authorized-keys; echo "rc=$?"rm -rf "$K"Le passage avec --fix affiche OK, le fichier compte de nouveau 2 lignes,
et le contrôle suivant affiche OK. La sauvegarde de la leçon précédente reste le garde-fou : --fix modifie la base ou le stockage, et il vaut mieux savoir
revenir en arrière avant de le lancer sur une instance réelle.
Comment réparer les hooks d'un dépôt ?
Section intitulée « Comment réparer les hooks d'un dépôt ? »Chaque dépôt porte des scripts (hooks) par lesquels Forgejo contrôle les
pushes ; doctor check --run hooks les compare à ce qu'il génère, et --fix
les régénère. Le hook pre-receive s'exécute sur le serveur avant
d'accepter un push : c'est lui qui, plus bas, refuse un push hors quota. Nous
l'avons supprimé du dépôt gc-demo (celui de la section sur le gc), vérifié sa
disparition (ls … | grep -c '^pre-receive$' rend 0) et lancé la
vérification.
R=/data/git/repositories/fjadmin/gc-demo.gitsudo docker exec forgejo sh -c "rm -f $R/hooks/pre-receive; ls $R/hooks | grep -c '^pre-receive$'"F doctor check --run hooks 2>&1; echo "rc=$?"F doctor check --run hooks --fix 2>&1; echo "rc=$?"sudo docker exec forgejo sh -c "ls -l $R/hooks/pre-receive"F doctor check --run hooks 2>&1; echo "rc=$?"Le premier passage affiche des lignes [W] (avertissement), notamment
old hook file …/hooks/pre-receive does not exist, puis OK et rc=0.
Les avertissements ne font donc pas changer le verdict : là encore, il faut lire
les lignes. Avec --fix, la sortie ajoute [W] Regenerated hooks for fjadmin/gc-demo, le fichier pre-receive est de retour (-rwxr-xr-x, 376
octets), et le dernier contrôle n'affiche plus que OK.
Une réserve. Sur un dépôt intact et récent, hooks affiche déjà quatre
lignes [W] (pre-receive.d/gitea, update.d/gitea, post-receive.d/gitea
et proc-receive « is out of date »), puis OK et rc=0. Le passage avec
--fix les régénère pour tous les dépôts de l'instance (ici gc-demo2
aussi), et le contrôle suivant n'affiche plus que OK. La cause de ces
lignes n'a pas été trouvée ; retenez que hooks peut signaler des fichiers
périmés sur un dépôt récent, et que --fix les régénère.
Comment plafonner un utilisateur par un quota ?
Section intitulée « Comment plafonner un utilisateur par un quota ? »Un quota se compose d'une règle (une limite en octets sur un sujet comme
size:repos:all), d'un groupe qui porte la règle, et d'une affectation de
l'utilisateur au groupe ; la fonctionnalité s'allume avec [quota] ENABLED.
Un quota plafonne l'espace qu'un utilisateur peut occuper, ici avec ses
dépôts. Le fichier de la section précédente a déjà posé FORGEJO__quota__ENABLED. Au
départ, GET /admin/quota/groups et GET /admin/quota/rules répondent [] :
aucune règle n'existe. Créons un utilisateur dev1, dont le mot de passe est
tiré au hasard et jamais affiché, et son jeton.
PW=$(openssl rand -hex 12)F admin user create --username dev1 --email dev1@example.test --password "$PW" --must-change-password=falseT1=$(F admin user generate-access-token --username dev1 --token-name q --scopes all --raw)u1() { local m=$1 p=$2; shift 2; curl -sk -w '\nHTTP=%{http_code}\n' -X "$m" -H "Authorization: token $T1" -H 'Content-Type: application/json' "$@" "https://forge.lab.test/api/v1$p"; }apic POST /admin/quota/rules -d '{"name":"depots-1mio","limit":1048576,"subjects":["size:repos:all"]}'apic POST /admin/quota/groups -d '{"name":"petits-depots"}'apic PUT /admin/quota/groups/petits-depots/rules/depots-1mio | tail -1apic PUT /admin/quota/groups/petits-depots/users/dev1 | tail -1apic GET /admin/quota/groups | cut -c1-700La limite est en octets : 1048576, soit 1 Mio. Les deux créations répondent
HTTP=201, les deux affectations HTTP=204, et la dernière commande montre le groupe petits-depots portant la règle depots-1mio ("subjects":["size:repos:all"]).
La consommation de l'utilisateur se lit en son nom avec u1 GET /user/quota,
ou par l'administrateur avec apic GET /admin/users/dev1/quota, qui ajoute le
nom du groupe. Au départ, tous les compteurs valent 0.
Quand le quota refuse-t-il un push ?
Section intitulée « Quand le quota refuse-t-il un push ? »Le contrôle porte sur l'usage déjà connu au moment du push : le push qui fait
dépasser la limite passe, et c'est le suivant qui est refusé. La documentation
parle d'un quota souple (« soft »), contrôlé avant l'action, encore en
développement. C'est le point à retenir avant de choisir une limite : un quota de 1 Mio ne garantit pas
qu'un dépôt reste sous 1 Mio, il garantit qu'on ne pousse plus une fois la
limite franchie. Le schéma suit les trois pushes de dev1 de haut en bas :
regardez ce que le hook compare à chaque fois : l'usage déjà connu, mis à
jour quelques secondes après un push, et jamais la taille du push en cours. La
ligne qui change entre le deuxième push (accepté) et le troisième (refusé) est
cet usage connu.
Voici les gestes. dev1 crée un dépôt, clone, puis pousse un fichier de 400 Ko, lit sa consommation, pousse un fichier de 1,5 Mo, relit sa consommation et
tente de créer un second dépôt.
u1 POST /user/repos -d '{"name":"q1","auto_init":true,"default_branch":"main"}' | tail -1H=(-c credential.helper= -c "http.extraHeader=Authorization: token $T1")git "${H[@]}" clone -q https://forge.lab.test/dev1/q1.git q && cd qgit config user.email d@example.test; git config user.name dhead -c 400000 /dev/urandom > a.bin; git add .; git commit -qm agit "${H[@]}" push origin main 2>&1 | tail -3; echo "rc=${PIPESTATUS[0]}"sleep 5; u1 GET /user/quotahead -c 1500000 /dev/urandom > b.bin; git add .; git commit -qm bgit "${H[@]}" push origin main 2>&1 | tail -6; echo "rc=${PIPESTATUS[0]}"sleep 5; u1 GET /user/quotau1 POST /user/repos -d '{"name":"q2"}' | cut -c1-300Avant le premier push, la consommation vaut "repos":{"public":23696,…} (le
dépôt vide avec son README). Le premier push est accepté (rc=0), et la
consommation lue 5 secondes après est 424318. Le compteur est mis à
jour de façon asynchrone : lu juste après le push, il affiche encore l'ancien
chiffre (nous l'avons vu passer de 23696 à 424318 en moins de 2 secondes), d'où
les sleep 5. Le second push, de 1,5 Mo, est accepté aussi (rc=0),
alors que la limite est de 1 048 576 octets, puisque l'usage connu (424318)
est encore sous la limite : la consommation passe à 1925303 octets (le
chiffre varie de quelques octets, les fichiers étant aléatoires). La création
d'un dépôt est ensuite refusée par HTTP=413 et le message
{"message":"quota exceeded","user_id":2,"username":"dev1"}.
cd ..git "${H[@]}" clone -q https://forge.lab.test/dev1/q1.git q3 && cd q3git config user.email d@example.test; git config user.name decho petit > c.txt; git add .; git commit -qm cgit "${H[@]}" push origin main 2>&1 | tail -6; echo "rc=${PIPESTATUS[0]}"u1 POST /repos/dev1/q1/issues -d '{"title":"t"}' | cut -c1-200Le cd .. sort du premier clone : sans lui, git clone créerait un clone
imbriqué sans identité Git, et le commit échouerait. Le clone fonctionne
malgré le dépassement (3912 Kio avec l'arbre de travail), et la création
d'un ticket répond HTTP=201. Le troisième push, de quelques octets,
est refusé :
remote:remote: Forgejo: Quota exceededremote:To https://forge.lab.test/dev1/q1.git ! [remote rejected] main -> main (pre-receive hook declined)error: failed to push some refs to 'https://forge.lab.test/dev1/q1.git'rc=1Le message Quota exceeded vient du hook pre-receive : le serveur a
refusé le push, l'utilisateur voit pre-receive hook declined. Le clone et les tickets continuent de marcher ; le push et la création de dépôt sont refusés. La sortie de l'utilisateur du groupe rétablit
l'écriture.
apic DELETE /admin/quota/groups/petits-depots/users/dev1 | tail -1git "${H[@]}" push origin main 2>&1 | tail -3; echo "rc=${PIPESTATUS[0]}"La suppression répond HTTP=204 et le push repasse (b4f976e..4bc94fc main -> main, rc=0 ; les empreintes varient). Pour en tirer une règle d'usage : une limite doit être choisie
en tenant compte du plus gros push attendu, car elle ne l'interrompt pas.
Où grossit la forge et comment le mesurer ?
Section intitulée « Où grossit la forge et comment le mesurer ? »L'essentiel de l'espace d'une forge se trouve dans les dépôts
(/data/git/repositories) et dans la base PostgreSQL ; les autres dossiers du
volume /data se mesurent un par un avec du -sk. Mesurer d'abord, c'est
savoir où regarder le jour où le disque se remplit. Le bloc passe en revue les
dossiers de /data que Forgejo utilise, dans le conteneur.
sudo docker exec forgejo sh -c 'cd /data; for d in git/repositories gitea/attachments gitea/lfs gitea/repo-archive gitea/actions_log gitea/actions_artifacts gitea/packages gitea/avatars gitea/repo-avatars gitea/indexers gitea/queues gitea/sessions gitea/log gitea/conf git/.ssh; do [ -e $d ] && du -sk $d || echo "(absent) $d"; done; echo; ls gitea'Voici nos mesures, en Kio, sur une instance de test à ce moment-là.
| Dossier | Taille (Kio) |
|---|---|
git/repositories | 3508 |
gitea/indexers | 96 |
gitea/queues | 40 |
gitea/repo-archive | 24 |
gitea/avatars | 16 |
gitea/attachments, actions_log, actions_artifacts, packages, repo-avatars, log | 4 chacun |
gitea/lfs, gitea/sessions | absents |
Le dossier git/repositories domine, et gitea/lfs n'existe pas encore : Forgejo crée ses dossiers à l'usage. Les dossiers à surveiller sont donc ceux des fonctions que vous activez (pièces jointes, paquets, artefacts et journaux
des Actions). La commande F doctor check --run paths affiche en complément les
chemins de la configuration, dont Repository Root Path (/data/git/repositories)
et Data Root Path (/data/gitea).
La base se mesure avec psql dans le conteneur forgejo-db, et les volumes
Docker de l'hôte avec du.
sudo docker exec forgejo-db psql -U forgejo -d forgejo -c "select pg_size_pretty(pg_database_size('forgejo')) as base"sudo docker exec forgejo-db psql -U forgejo -d forgejo -c "select relname, pg_size_pretty(pg_total_relation_size(c.oid)) as taille from pg_class c join pg_namespace n on n.oid=c.relnamespace where n.nspname='public' and c.relkind='r' order by pg_total_relation_size(c.oid) desc limit 6"sudo du -sk /var/lib/docker/volumes/forgejo-lab_forgejo-data/_data /var/lib/docker/volumes/forgejo-lab_postgres-data/_dataLa base pèse 16 MB, et les plus grosses tables sont repository (304 kB),
issue (208 kB), action (184 kB), access_token (144 kB), user (128 kB) et
action_task (112 kB). Côté hôte, le volume forgejo-data pèse 3804 Kio et
le volume postgres-data 72524 Kio : le dossier du volume est plus gros que
la base mesurée par pg_database_size, et nous n'avons pas cherché pourquoi.
Dimensionnez le disque d'après le volume, pas d'après la seule base.
Comment savoir qu'une tâche planifiée a échoué ?
Section intitulée « Comment savoir qu'une tâche planifiée a échoué ? »Une tâche en échec écrit une notice dans la table notice de la base et une
ligne [W] ou [E] dans le journal du conteneur ; aucune API ne les expose.
GET /api/v1/admin/notices répond 404 page not found. Avec
NOTICE_ON_SUCCESS, une tâche écrit aussi une notice à chaque succès, ce qui
permet de vérifier qu'elle tourne. Pour provoquer un échec, nous avons créé un
dépôt gc-broken puis renommé son dossier objects, ce qui le rend illisible
pour Git.
apic POST /user/repos -d '{"name":"gc-broken","auto_init":true,"default_branch":"main"}' | tail -1B=/data/git/repositories/fjadmin/gc-broken.gitsudo docker exec forgejo sh -c "mv $B/objects $B/objects.bak"apic POST /admin/cron/repo_health_check | tail -1apic POST /admin/cron/git_gc_repos | tail -1sleep 10sudo docker exec forgejo-db psql -U forgejo -d forgejo -c "select id, type, left(replace(description, E'\n', ' '), 230) as description from notice order by id"sudo docker logs forgejo --since 1m 2>&1 | grep -iE "\[E\]|\[W\]|health|gc-broken" | cut -c1-250 | head -8sudo docker exec forgejo sh -c "mv $B/objects.bak $B/objects"Les deux déclenchements répondent HTTP=204, ce qui confirme que 204 ne dit
pas que la tâche a réussi. La table contient alors cinq lignes dans notre rejeu (le nombre dépend
des passages du planning toutes les deux minutes). Trois sont de
type 2 : Cron: Garbage collect all repositories has finished (le succès
demandé par NOTICE_ON_SUCCESS). Deux sont de type 1, les échecs :
Failed to health check repository (fjadmin/gc-broken): exit status 128 et
Repository garbage collection failed for /data/git/repositories/fjadmin/gc-broken.git. Stdout: Error: exit status 128 - fatal: not a git repository….
Le journal du conteneur dit la même chose, avec le niveau :
.../repository/check.go:52:GitFsckRepo() [W] Failed to health check repository (<Repository 2:fjadmin/gc-broken>): exit status 128.../repository/check.go:94:GitGcRepo() [E] Repository garbage collection failed for <Repository 2:fjadmin/gc-broken>. Stdout:La supervision d'une forge peut donc lire ces deux sources : la table notice (requête SQL) et les lignes [E] du journal. Dans nos essais, le
type 1 accompagne les échecs et le type 2 les succès ; nous n'avons pas lu les
notices dans l'interface web d'administration. Le dossier objects est remis
en place par la dernière commande.
Que faire quand la maintenance ne se passe pas comme prévu ?
Section intitulée « Que faire quand la maintenance ne se passe pas comme prévu ? »Les échecs de cette leçon se lisent dans la sortie de doctor, dans les
notices, dans le message du hook ou dans l'état de app.ini. Le tableau
relie chaque symptôme à sa cause.
| Symptôme | Cause | Correction |
|---|---|---|
fatal: detected dubious ownership in repository avec git count-objects | la commande tourne en root alors que le dépôt appartient à git | docker exec -u git forgejo git -C <dépôt> count-objects -v |
Command error: unknown checks: "check-hooks" (code 1) | le nom de la vérification n'existe pas | forgejo doctor check --list, le nom est hooks |
doctor affiche [E] et ERROR, code de sortie 0 | le code ne reflète pas le verdict | lire le texte, relancer avec --fix |
[I] AppPath set to '/usr/local/bin/gitea' | information normale de authorized-keys | rien à faire |
hooks signale is out of date sur un dépôt récent | cause non trouvée | --run hooks --fix |
Une tâche n'a jamais d'échéance (-, 0001-01-01) | désactivée par défaut | ENABLED et SCHEDULE dans un fichier de surcharge |
POST /admin/cron/<tâche> répond 204 mais rien ne change | la demande est acceptée, pas validée | lire exec_times, la notice et le journal |
| La valeur d'une tâche reste après retrait de la variable | app.ini garde la clé | fixer explicitement la valeur voulue |
remote: Forgejo: Quota exceeded et pre-receive hook declined | l'usage connu dépasse la limite | sortir l'utilisateur du groupe |
HTTP 413 quota exceeded à la création d'un dépôt | même cause | même correction |
GET /api/v1/admin/notices répond 404 | cette API n'existe pas en 15.0.10 | lire la table notice |
Le diagnostic commence par une question : la vérification est-elle
disponible sous ce nom, et quelle ligne est en [E] ? Ensuite, selon la
réponse, soit --fix (fichiers de l'instance), soit une action sur la tâche
(planning, POST), soit une action sur l'utilisateur (quota).
Comment défaire cette configuration ?
Section intitulée « Comment défaire cette configuration ? »On supprime le groupe puis la règle de quota, les dépôts et l'utilisateur de
test, la clé SSH et les notices, on remet authorized_keys d'aplomb, puis on
détruit le jeton. Chaque suppression par l'API répond 204.
apic DELETE /admin/quota/groups/petits-depots | tail -1apic DELETE /admin/quota/rules/depots-1mio | tail -1for r in fjadmin/gc-demo fjadmin/gc-demo2 fjadmin/gc-broken dev1/q1; do echo -n "$r "; curl -sk -o /dev/null -w '%{http_code}\n' -X DELETE -H "Authorization: token $(cat ~/.tok)" https://forge.lab.test/api/v1/repos/$r; doneecho -n "dev1 "; curl -sk -o /dev/null -w '%{http_code}\n' -X DELETE -H "Authorization: token $(cat ~/.tok)" "https://forge.lab.test/api/v1/admin/users/dev1?purge=true"KID=$(api GET /user/keys | python3 -c 'import sys,json; print(" ".join(str(k["id"]) for k in json.load(sys.stdin)))')for k in $KID; do curl -sk -o /dev/null -w "cle $k %{http_code}\n" -X DELETE -H "Authorization: token $(cat ~/.tok)" https://forge.lab.test/api/v1/user/keys/$k; donesudo docker exec forgejo-db psql -U forgejo -d forgejo -Atc "delete from notice"F doctor check --run authorized-keys --fix | tail -3shred -u ~/.toksudo docker exec forgejo-db psql -U forgejo -d forgejo -Atc "delete from access_token; select count(*) from access_token"Les dépôts, l'utilisateur et la clé répondent 204, DELETE 5 (le nombre varie) retire les
notices, et la dernière commande affiche DELETE 1 puis 0. Attention : ce
dernier DELETE supprime tous les jetons de l'instance, ce qui ne convient
qu'à un lab jetable ; sur une forge réelle, révoquez le seul jeton l10
comme dans la leçon
Assister les utilisateurs.
Le groupe et la règle vides ramènent GET /admin/quota/groups à [].
Le fichier compose.maintenance.yaml et la ligne COMPOSE_FILE ne sont pas
retirés par ce bloc. Retirer le fichier de COMPOSE_FILE ne vide pas app.ini (voir « Que devient une clé quand on retire sa variable ? ») : les
sections [cron.git_gc_repos], [cron.delete_inactive_accounts],
[cron.archive_cleanup] et [quota] y resteraient. Sur une pile qui n'avait
que ce fichier dans COMPOSE_FILE, nous avons retiré la ligne et le fichier,
supprimé ces sections en tant que git (jamais en root), puis
redémarré le conteneur.
sudo sed -i '/^COMPOSE_FILE=/d' .envsudo rm -f compose.maintenance.yamlsudo docker compose up -dsudo docker exec -u git forgejo sh -c "sed -i '/^\[cron\./,/^\$/d;/^\[quota\]/,/^\$/d' /data/gitea/conf/app.ini"sudo docker compose restart serverLe contrôle grep -cE '^\[(cron|quota)' /data/gitea/conf/app.ini, lancé dans
le conteneur, rend alors 0. Si votre COMPOSE_FILE porte d'autres fichiers,
retirez seulement :compose.maintenance.yaml de la liste.
À retenir
Section intitulée « À retenir »- Forgejo planifie 30 tâches de fond, visibles par
GET /api/v1/admin/cronet non par la CLI ; treize n'ont pas de planning par défaut, dontgit_gc_repos,delete_repo_archivesetdelete_old_actions. POST /api/v1/admin/cron/<tâche>répond204même pour une tâche désactivée et la lance : le204ne prouve pas le succès, l'effet se mesure sur le disque et l'échec se lit dans les notices.git_gc_reposa fait passer un dépôt de 40 pushes de 1872 à 1024 Kio ;delete_repo_archivessupprime toutes les archives (le fichier.zipdisparaît, le dossier passe de 28 à 24 Kio). Sur une instance vide, les autres tâches n'ont eu aucun effet mesurable.git count-objectsenrootéchoue surdubious ownership: passer pardocker exec -u git.- Le planning se règle par
FORGEJO__cron.<tâche>__ENABLED,__SCHEDULE,__NOTICE_ON_SUCCESS,__OLDER_THANdans un fichier de surcharge deCOMPOSE_FILE; retirer la variable ne retire pas la clé d'app.ini, il faut fixer la valeur voulue. forgejo doctor check --run <nom> --fixrépare ce qui peut l'être ; le nom doit être exact (hooks, pascheck-hooks), et le code de sortie vaut 0 même avecERROR: une supervision lit le texte.- Un quota se compose d'une règle (
limiten octets,subjectssize:repos:all), d'un groupe et d'une affectation ; le push qui fait dépasser passe, le suivant est refusé (Quota exceeded), la création de dépôt répond 413, le clone et les tickets restent possibles. - Le disque se mesure avec
du -skpar dossier de/dataetpsqlpour la base ; les dépôts dominent sur une instance neuve. - Il n'existe pas d'API des notices ; la table
noticeet le journal du conteneur ([W],[E]) portent les échecs.
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Six questions tirées sur huit : où lire les tâches planifiées, ce que prouve un lancement à la demande, la clé qui reste dans app.ini, le code de sortie de doctor, le quota et le nom exact d'une vérification.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Score par domaine
Un domaine regroupe plusieurs guides et reçoit plusieurs questions : son score est plus fiable qu'un score par thème, qui porte souvent sur une seule question.
Bilan
Points forts
Aucun sujet réussi à 80 % sur au moins deux questions pour l’instant.
Points faibles
Aucun sujet sous le seuil de réussite.
À faire ensuite
Des indices pour retenter votre chance ?
Continuer le cours : Monter de version avec ses critères de validation
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications