Compare commits
1 Commits
docs/RUNBO
...
chore/dock
| Author | SHA1 | Date | |
|---|---|---|---|
| bb4610f49d |
@@ -7,16 +7,18 @@ WORKDIR /app
|
|||||||
ENV npm_config_update_notifier=false \
|
ENV npm_config_update_notifier=false \
|
||||||
npm_config_audit=false \
|
npm_config_audit=false \
|
||||||
npm_config_fund=false \
|
npm_config_fund=false \
|
||||||
npm_config_progress=false
|
npm_config_progress=false \
|
||||||
|
ASTRO_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
# (Optionnel mais propre) git + certificats
|
# (Optionnel mais propre) git + certificats
|
||||||
RUN apt-get -o Acquire::Retries=5 -o Acquire::ForceIPv4=true update \
|
RUN apt-get -o Acquire::Retries=5 -o Acquire::ForceIPv4=true update \
|
||||||
&& apt-get install -y --no-install-recommends ca-certificates git \
|
&& apt-get install -y --no-install-recommends ca-certificates git \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Déps d’abord (cache Docker)
|
# Déps d’abord (cache Docker)
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci --no-audit --no-fund
|
RUN --mount=type=cache,target=/root/.npm \
|
||||||
|
npm ci --no-audit --no-fund
|
||||||
|
|
||||||
# Sources
|
# Sources
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -1,122 +0,0 @@
|
|||||||
# RUNBOOK — Créer une Demande d’ajout (PR) “automatique” depuis un push (Gitea)
|
|
||||||
|
|
||||||
## Objectif
|
|
||||||
Pousser une branche depuis le Mac vers Gitea et obtenir le workflow standard :
|
|
||||||
1) branche dédiée
|
|
||||||
2) push
|
|
||||||
3) suggestion “Nouvelle demande d’ajout” (bandeau vert) OU lien terminal
|
|
||||||
4) création PR via UI
|
|
||||||
5) merge (main protégé)
|
|
||||||
|
|
||||||
> Important : Gitea ne crée pas une PR automatiquement.
|
|
||||||
> Il affiche une *suggestion* (bandeau vert) ou imprime un lien “Create a new pull request” lors du push.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pré-check (obligatoire, 10 secondes)
|
|
||||||
en bash :
|
|
||||||
|
|
||||||
git status -sb
|
|
||||||
git fetch origin --prune
|
|
||||||
git branch --show-current
|
|
||||||
|
|
||||||
Procédure standard (zéro surprise)
|
|
||||||
### 1) Se remettre propre sur main
|
|
||||||
|
|
||||||
git checkout main
|
|
||||||
git pull --ff-only
|
|
||||||
|
|
||||||
### 2) Créer une branche AVANT de modifier / ajouter des fichiers
|
|
||||||
|
|
||||||
git switch -c docs/<YYYY-MM-DD>-<sujet-court>
|
|
||||||
|
|
||||||
### 3) Ajouter/modifier tes fichiers dans docs/
|
|
||||||
|
|
||||||
Exemple :
|
|
||||||
|
|
||||||
docs/auth-stack.md
|
|
||||||
|
|
||||||
docs/runbook-....
|
|
||||||
|
|
||||||
### 4) Vérifier ce qui va partir
|
|
||||||
|
|
||||||
git status -sb
|
|
||||||
git diff
|
|
||||||
|
|
||||||
### 5) Commit
|
|
||||||
|
|
||||||
git add docs/
|
|
||||||
git commit -m "docs: <résumé clair>"
|
|
||||||
|
|
||||||
### 6) Vérifier que ta branche a bien des commits “devant” main (SINON pas de PR possible)
|
|
||||||
|
|
||||||
git fetch origin
|
|
||||||
git log --oneline origin/main..HEAD
|
|
||||||
|
|
||||||
Si ça n’affiche rien : tu n’as rien à proposer (branche identique à main).
|
|
||||||
|
|
||||||
### 7) Push (méthode la plus robuste)
|
|
||||||
|
|
||||||
git push -u origin HEAD
|
|
||||||
|
|
||||||
### 8) Créer la PR (2 chemins fiables)
|
|
||||||
# Chemin A — le plus simple : utiliser le lien imprimé dans le terminal
|
|
||||||
|
|
||||||
Après le push, Gitea affiche généralement :
|
|
||||||
“Create a new pull request for '<ta-branche>': <URL>”
|
|
||||||
➡️ Ouvre cette URL, clique “Créer la demande d’ajout”.
|
|
||||||
|
|
||||||
# Chemin B — via l’UI Gitea (si tu veux le bandeau vert)
|
|
||||||
|
|
||||||
Va sur le dépôt
|
|
||||||
|
|
||||||
Onglet “Demandes d’ajout”
|
|
||||||
|
|
||||||
Clique “Nouvelle demande d’ajout”
|
|
||||||
|
|
||||||
Source branch = ta branche, Target = main
|
|
||||||
|
|
||||||
Créer
|
|
||||||
|
|
||||||
## Pourquoi le bandeau vert peut ne PAS apparaître (et ce que ça signifie)
|
|
||||||
|
|
||||||
Ta branche est identique à main
|
|
||||||
|
|
||||||
# Diagnostic :
|
|
||||||
|
|
||||||
git fetch origin
|
|
||||||
git diff --name-status origin/main..HEAD
|
|
||||||
|
|
||||||
Si vide => normal, pas de suggestion.
|
|
||||||
|
|
||||||
Tu n’es pas sur la bonne branche
|
|
||||||
|
|
||||||
# Diagnostic :
|
|
||||||
|
|
||||||
git branch --show-current
|
|
||||||
|
|
||||||
Tu regardes l’UI au mauvais endroit
|
|
||||||
|
|
||||||
Solution : utilise le bouton “Nouvelle demande d’ajout” ou le lien du terminal (chemin A).
|
|
||||||
|
|
||||||
Anti-bêtise (optionnel mais recommandé)
|
|
||||||
Empêcher de commit sur main par erreur (hook local)
|
|
||||||
|
|
||||||
# Créer .git/hooks/pre-commit :
|
|
||||||
|
|
||||||
#!/bin/sh
|
|
||||||
b="$(git branch --show-current)"
|
|
||||||
if [ "$b" = "main" ]; then
|
|
||||||
echo "❌ Refus: commit interdit sur main. Crée une branche."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
Puis :
|
|
||||||
|
|
||||||
chmod +x .git/hooks/pre-commit
|
|
||||||
|
|
||||||
## Rappel : main protégé
|
|
||||||
|
|
||||||
Si main est protégé, tu ne merges PAS par git push origin main.
|
|
||||||
Tu merges via la PR (UI), après CI verte.
|
|
||||||
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
# Auth Stack — LLDAP + Authelia + Redis (DSM 7.3 / Synology DS220+)
|
|
||||||
|
|
||||||
## Objectif
|
|
||||||
Fournir une pile d’authentification robuste (anti-lockout) pour protéger des services web via reverse-proxy :
|
|
||||||
- Annuaire utilisateurs : **LLDAP**
|
|
||||||
- Portail / SSO / MFA : **Authelia**
|
|
||||||
- Cache/sessions (optionnel selon config) : **Redis**
|
|
||||||
- Exposition publique : **Reverse proxy** (Synology / Nginx / Traefik) vers Authelia
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Composants
|
|
||||||
- **LLDAP**
|
|
||||||
- UI admin (HTTP) : `127.0.0.1:17170`
|
|
||||||
- LDAP : `127.0.0.1:3890`
|
|
||||||
- Base : sqlite dans `/volume2/docker/auth/data/lldap`
|
|
||||||
|
|
||||||
- **Authelia**
|
|
||||||
- API/portal : `127.0.0.1:9091`
|
|
||||||
- Stockage : sqlite dans `/volume2/docker/auth/data/authelia/db.sqlite3`
|
|
||||||
- Accès externe : via reverse proxy -> `https://auth.<domaine>`
|
|
||||||
|
|
||||||
- **Redis**
|
|
||||||
- Local uniquement : `127.0.0.1:6379`
|
|
||||||
- (peut servir plus tard à sessions/rate-limit selon config)
|
|
||||||
|
|
||||||
### Exposition réseau (principe de sécurité)
|
|
||||||
- Tous les services **bindés sur 127.0.0.1** (loopback NAS)
|
|
||||||
- Seul le **reverse proxy** expose `https://auth.<domaine>` vers `127.0.0.1:9091`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Fichiers de référence
|
|
||||||
|
|
||||||
### 1) docker-compose.auth.yml
|
|
||||||
- Déploie redis + lldap + authelia.
|
|
||||||
- Recommandation DSM : **network_mode: host** + bind sur localhost.
|
|
||||||
- Supprime les aléas “bridge + DNS + subnets”
|
|
||||||
- Évite les timeouts LDAP sporadiques.
|
|
||||||
|
|
||||||
### 2) /volume2/docker/auth/compose/.env
|
|
||||||
Variables attendues :
|
|
||||||
|
|
||||||
#### LLDAP
|
|
||||||
- `LLDAP_JWT_SECRET=...` (random 32+)
|
|
||||||
- `LLDAP_KEY_SEED=...` (random 32+)
|
|
||||||
- `LLDAP_LDAP_USER_PASS=...` (mot de passe admin LLDAP)
|
|
||||||
|
|
||||||
#### Authelia
|
|
||||||
- `AUTHELIA_JWT_SECRET=...` (utilisé ici comme source pour reset_password)
|
|
||||||
- `AUTHELIA_SESSION_SECRET=...`
|
|
||||||
- `AUTHELIA_STORAGE_ENCRYPTION_KEY=...`
|
|
||||||
|
|
||||||
> Ne jamais committer `.env`. Stocker dans DSM / secrets.
|
|
||||||
|
|
||||||
### 3) /volume2/docker/auth/config/authelia/configuration.yml
|
|
||||||
- LDAP address en mode robuste : `ldap://127.0.0.1:3890`
|
|
||||||
- Cookie domain : `archicratie.trans-hands.synology.me`
|
|
||||||
- `authelia_url` : `https://auth.archicratie.trans-hands.synology.me`
|
|
||||||
- `default_redirection_url` : service principal (ex: gitea)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Procédures opératoires
|
|
||||||
|
|
||||||
### Restart safe (redémarrage propre)
|
|
||||||
en bash :
|
|
||||||
cd /volume2/docker/auth/compose
|
|
||||||
sudo docker compose --env-file .env -f docker-compose.auth.yml down --remove-orphans
|
|
||||||
sudo docker compose --env-file .env -f docker-compose.auth.yml up -d --force-recreate
|
|
||||||
|
|
||||||
### Tests santé (sans dépendances DSM)
|
|
||||||
curl -fsS http://127.0.0.1:17170/ >/dev/null && echo "LLDAP UI OK"
|
|
||||||
curl -fsS http://127.0.0.1:9091/api/health && echo "AUTHELIA LOCAL OK"
|
|
||||||
curl -kfsS https://auth.archicratie.trans-hands.synology.me/api/health && echo "AUTHELIA HTTPS OK"
|
|
||||||
|
|
||||||
### Test TCP LDAP :
|
|
||||||
sudo docker run --rm --network host nicolaka/netshoot:latest sh -lc 'nc -vz -w2 127.0.0.1 3890'
|
|
||||||
|
|
||||||
### Rotate secrets (rotation)
|
|
||||||
|
|
||||||
# Principes :
|
|
||||||
|
|
||||||
Rotation = redémarrage forcé d’Authelia (sessions invalidées)
|
|
||||||
|
|
||||||
Rotation de LLDAP_KEY_SEED est sensible : peut affecter chiffrement des mots de passe.
|
|
||||||
|
|
||||||
# Procédure conseillée :
|
|
||||||
|
|
||||||
Sauvegarder DBs :
|
|
||||||
|
|
||||||
/volume2/docker/auth/data/lldap/users.db
|
|
||||||
|
|
||||||
/volume2/docker/auth/data/authelia/db.sqlite3
|
|
||||||
|
|
||||||
Changer d’abord secrets Authelia (AUTHELIA_SESSION_SECRET, AUTHELIA_STORAGE_ENCRYPTION_KEY)
|
|
||||||
|
|
||||||
docker compose up -d --force-recreate authelia
|
|
||||||
|
|
||||||
Vérifier /api/health + login.
|
|
||||||
|
|
||||||
Reset admin LLDAP (break-glass)
|
|
||||||
|
|
||||||
# Si tu perds le mot de passe admin :
|
|
||||||
|
|
||||||
Activer temporairement LLDAP_FORCE_LDAP_USER_PASS_RESET=true dans l’environnement LLDAP
|
|
||||||
|
|
||||||
Redémarrer LLDAP une seule fois
|
|
||||||
|
|
||||||
Désactiver immédiatement après.
|
|
||||||
|
|
||||||
⚠️ Ne jamais laisser ce flag en permanence : il force le reset à chaque boot.
|
|
||||||
|
|
||||||
## Checklist anti-lockout (indispensable)
|
|
||||||
### 1) Accès direct local (bypass)
|
|
||||||
|
|
||||||
LLDAP UI accessible en local : http://127.0.0.1:17170
|
|
||||||
|
|
||||||
Authelia health local : http://127.0.0.1:9091/api/health
|
|
||||||
|
|
||||||
### 2) Règle Authelia : domaine auth en bypass
|
|
||||||
|
|
||||||
Dans configuration.yml :
|
|
||||||
access_control:
|
|
||||||
rules:
|
|
||||||
- domain: "auth.<domaine>"
|
|
||||||
policy: bypass
|
|
||||||
|
|
||||||
But : pouvoir charger le portail même si les règles des autres domaines cassent.
|
|
||||||
|
|
||||||
### 3) Route de secours reverse-proxy
|
|
||||||
|
|
||||||
Prévoir une route non protégée (ou protégée différemment) pour pouvoir corriger :
|
|
||||||
|
|
||||||
ex: https://admin.<domaine>/ ou un vhost interne LAN-only.
|
|
||||||
|
|
||||||
### 4) Fenêtre privée pour tester
|
|
||||||
|
|
||||||
Toujours tester login/authelia dans un onglet privé pour éviter cookies “fantômes”.
|
|
||||||
|
|
||||||
## Troubleshooting (ce qu’on a rencontré et résolu)
|
|
||||||
### A) YAML/Compose cassé (tabs, doublons)
|
|
||||||
|
|
||||||
# Symptômes :
|
|
||||||
|
|
||||||
mapping key "ports" already defined
|
|
||||||
|
|
||||||
found character that cannot start any token
|
|
||||||
|
|
||||||
# Fix :
|
|
||||||
|
|
||||||
supprimer tabs
|
|
||||||
|
|
||||||
supprimer doublons (volumes/ports/networks)
|
|
||||||
|
|
||||||
valider : docker compose ... config
|
|
||||||
|
|
||||||
### B) Substitution foireuse des variables dans healthcheck
|
|
||||||
|
|
||||||
# Problème :
|
|
||||||
|
|
||||||
$VAR évalué par compose au parse-time
|
|
||||||
|
|
||||||
# Fix :
|
|
||||||
|
|
||||||
utiliser $$VAR dans CMD-SHELL si nécessaire.
|
|
||||||
|
|
||||||
### C) /config monté read-only
|
|
||||||
|
|
||||||
# Symptômes :
|
|
||||||
|
|
||||||
chown: /config/... Read-only file system
|
|
||||||
|
|
||||||
# Fix :
|
|
||||||
|
|
||||||
monter /config en :rw si Authelia doit écrire des backups/keys.
|
|
||||||
|
|
||||||
### D) Timeouts LDAP aléatoires en bridge
|
|
||||||
|
|
||||||
# Symptômes :
|
|
||||||
|
|
||||||
dial tcp <ip>:3890: i/o timeout
|
|
||||||
|
|
||||||
IP Docker “surprise” (subnet 192.168.32.0/20 etc.)
|
|
||||||
|
|
||||||
# Fix robuste DSM :
|
|
||||||
|
|
||||||
passer en network_mode: host + bind 127.0.0.1
|
|
||||||
|
|
||||||
Authelia -> ldap://127.0.0.1:3890
|
|
||||||
|
|
||||||
### E) “Authelia OK mais Gitea redemande login”
|
|
||||||
|
|
||||||
# Normal :
|
|
||||||
|
|
||||||
tant que Gitea n’est pas configuré en OIDC vers Authelia, ce n’est pas du SSO.
|
|
||||||
|
|
||||||
Authelia protège l’accès, mais ne crée pas de session Gitea.
|
|
||||||
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# Workflow Git/Gitea — main protégé (PR only)
|
|
||||||
|
|
||||||
## Objectif
|
|
||||||
Éviter toute casse de `main` : on travaille **toujours** via branche + Pull Request.
|
|
||||||
|
|
||||||
## 1) Démarrer propre (local)
|
|
||||||
en bash :
|
|
||||||
|
|
||||||
git fetch origin --prune
|
|
||||||
git checkout main
|
|
||||||
git reset --hard origin/main
|
|
||||||
git clean -fd
|
|
||||||
|
|
||||||
## 2) Créer une branche
|
|
||||||
|
|
||||||
git checkout -b fix/ma-modif
|
|
||||||
|
|
||||||
## 3) Modifier, tester, commit
|
|
||||||
|
|
||||||
npm test
|
|
||||||
git add -A
|
|
||||||
git commit -m "Mon changement"
|
|
||||||
|
|
||||||
## 4) Push (création branche distante)
|
|
||||||
|
|
||||||
git push -u origin fix/ma-modif
|
|
||||||
|
|
||||||
## 5) Créer la Pull Request (UI Gitea)
|
|
||||||
|
|
||||||
Gitea → repository → Pull Requests → New Pull Request
|
|
||||||
|
|
||||||
base : main
|
|
||||||
compare : fix/ma-modif
|
|
||||||
|
|
||||||
Si “je ne vois pas de PR”
|
|
||||||
|
|
||||||
Vérifie d’abord qu’il y a un diff réel :
|
|
||||||
|
|
||||||
git log --oneline origin/main..HEAD
|
|
||||||
|
|
||||||
Si la commande ne sort rien : ta branche ne contient aucun commit différent → PR inutile/invisible.
|
|
||||||
|
|
||||||
## 6) Conflits
|
|
||||||
|
|
||||||
Ne merge pas en local vers main (push refusé si main protégé).
|
|
||||||
On met à jour la branche de PR :
|
|
||||||
|
|
||||||
Option A (simple) : merge main dans la branche
|
|
||||||
|
|
||||||
git fetch origin
|
|
||||||
git merge origin/main
|
|
||||||
# résoudre conflits
|
|
||||||
npm test
|
|
||||||
git push
|
|
||||||
|
|
||||||
Option B (plus propre) : rebase
|
|
||||||
|
|
||||||
git fetch origin
|
|
||||||
git rebase origin/main
|
|
||||||
# résoudre conflits, puis:
|
|
||||||
npm test
|
|
||||||
git push --force-with-lease
|
|
||||||
|
|
||||||
## 7) Merge
|
|
||||||
|
|
||||||
Toujours depuis l’UI de la Pull Request (ou via un mainteneur).
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# “Proposer” protégé par groupe (whoami / editors)
|
|
||||||
|
|
||||||
## But
|
|
||||||
Le bouton **Proposer** (création d’issue Gitea pré-remplie) doit être :
|
|
||||||
- visible **uniquement** pour les membres du groupe `editors`,
|
|
||||||
- **absent** pour les autres utilisateurs,
|
|
||||||
- robuste (fail-closed), mais **non-collant** (pas de “bloqué” après un échec transitoire).
|
|
||||||
|
|
||||||
## Pré-requis (build-time)
|
|
||||||
Les variables publiques Astro doivent être injectées au build :
|
|
||||||
- `PUBLIC_GITEA_BASE`
|
|
||||||
- `PUBLIC_GITEA_OWNER`
|
|
||||||
- `PUBLIC_GITEA_REPO`
|
|
||||||
|
|
||||||
Si une seule manque → `giteaReady=false` → Proposer est désactivé.
|
|
||||||
|
|
||||||
### Vérification NAS (slots blue/green)
|
|
||||||
Exemple :
|
|
||||||
- blue : http://127.0.0.1:8081/...
|
|
||||||
- green : http://127.0.0.1:8082/...
|
|
||||||
|
|
||||||
Commande (ex) :
|
|
||||||
`curl -sS http://127.0.0.1:8081/archicratie/archicrat-ia/chapitre-4/ | grep -n "const GITEA_" | head`
|
|
||||||
|
|
||||||
## Signal d’auth (runtime) : `/_auth/whoami`
|
|
||||||
Le site appelle `/_auth/whoami` (same-origin) pour récupérer :
|
|
||||||
- `Remote-User`
|
|
||||||
- `Remote-Groups`
|
|
||||||
Ces headers sont injectés par la chaîne edge (Traefik → Authelia forward-auth).
|
|
||||||
|
|
||||||
### Appel robuste
|
|
||||||
- cache-bust : `?_=${Date.now()}`
|
|
||||||
- `cache: "no-store"`
|
|
||||||
- `credentials: "include"`
|
|
||||||
|
|
||||||
### Critère
|
|
||||||
`groups.includes("editors")`
|
|
||||||
|
|
||||||
## Comportement attendu (UX)
|
|
||||||
- utilisateur editors : le bouton “Proposer” est visible, ouvre la modal, puis ouvre Gitea.
|
|
||||||
- utilisateur non editors : le bouton “Proposer” n’existe pas (retiré du DOM).
|
|
||||||
|
|
||||||
## Pièges connus
|
|
||||||
1) Tester en direct 8081/8082 ne reflète pas toujours la chaîne Traefik+Authelia.
|
|
||||||
2) Un gate “collant” peut rester OFF si l’échec est mis en cache trop agressivement.
|
|
||||||
3) Si “Proposer” est caché via `style.display="none"`, il faut le réafficher via `style.display=""` (pas via `hidden=false`).
|
|
||||||
|
|
||||||
## Debug rapide (console navigateur)
|
|
||||||
en js :
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
const r = await fetch("/_auth/whoami?_=" + Date.now(), {
|
|
||||||
credentials: "include",
|
|
||||||
cache: "no-store",
|
|
||||||
redirect: "follow",
|
|
||||||
});
|
|
||||||
const t = await r.text();
|
|
||||||
const groups = (t.match(/^Remote-Groups:\s*(.*)$/mi)?.[1] || "")
|
|
||||||
.split(",").map(s => s.trim()).filter(Boolean);
|
|
||||||
console.log({ ok: r.ok, status: r.status, groups, raw: t.slice(0, 220) + "..." });
|
|
||||||
})();
|
|
||||||
|
|
||||||
## Définition “done”
|
|
||||||
|
|
||||||
Archicratia (editors) voit Proposer et peut ouvrir un ticket.
|
|
||||||
|
|
||||||
s-FunX (non editors) ne voit pas Proposer.
|
|
||||||
|
|
||||||
Les deux slots blue/green injectent les constantes Gitea dans le HTML.
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
# Runbook — Déploiement Archicratie Web Édition (Blue/Green)
|
|
||||||
|
|
||||||
## Arborescence NAS (repère)
|
|
||||||
- `/volume2/docker/archicratie-web/current/` : état courant (Dockerfile, docker-compose.yml, dist buildé en image)
|
|
||||||
- `/volume2/docker/archicratie-web/releases/` : historiques éventuels
|
|
||||||
- `/volume2/docker/edge/` : Traefik + config dynamique
|
|
||||||
|
|
||||||
> Important : les commandes `docker compose -f ...` doivent viser le **docker-compose.yml présent dans `current/`**.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pré-requis Synology
|
|
||||||
Sur NAS, les commandes ont été exécutées avec :
|
|
||||||
en bash
|
|
||||||
sudo env DOCKER_API_VERSION=1.43 docker ...
|
|
||||||
|
|
||||||
(contexte DSM / compat API)
|
|
||||||
|
|
||||||
### 1) Variables de build (Gitea)
|
|
||||||
|
|
||||||
Dans /volume2/docker/archicratie-web/current créer/maintenir :
|
|
||||||
|
|
||||||
cat > .env <<'EOF'
|
|
||||||
PUBLIC_GITEA_BASE=https://gitea.archicratie.trans-hands.synology.me
|
|
||||||
PUBLIC_GITEA_OWNER=Archicratia
|
|
||||||
PUBLIC_GITEA_REPO=archicratie-edition
|
|
||||||
EOF
|
|
||||||
|
|
||||||
### 2) Build images (blue + green) — méthode robuste
|
|
||||||
|
|
||||||
cd /volume2/docker/archicratie-web/current
|
|
||||||
|
|
||||||
sudo env DOCKER_API_VERSION=1.43 docker compose -f docker-compose.yml build --no-cache web_blue web_green
|
|
||||||
|
|
||||||
Puis recréer les conteneurs sans rebuild :
|
|
||||||
|
|
||||||
sudo env DOCKER_API_VERSION=1.43 docker compose -f docker-compose.yml up -d --force-recreate --no-build web_blue web_green
|
|
||||||
|
|
||||||
### 3) Vérifier que les deux slots sont OK
|
|
||||||
|
|
||||||
curl -sS -D- http://127.0.0.1:8081/ | head -n 12
|
|
||||||
curl -sS -D- http://127.0.0.1:8082/ | head -n 12
|
|
||||||
|
|
||||||
Attendu :
|
|
||||||
|
|
||||||
HTTP/1.1 200 OK
|
|
||||||
|
|
||||||
Server: nginx/...
|
|
||||||
|
|
||||||
### 4) Traefik : s’assurer qu’un seul backend est actif
|
|
||||||
|
|
||||||
Fichier :
|
|
||||||
/volume2/docker/edge/config/dynamic/20-archicratie-backend.yml
|
|
||||||
|
|
||||||
Attendu : une seule URL (8081 OU 8082)
|
|
||||||
|
|
||||||
http:
|
|
||||||
services:
|
|
||||||
archicratie_web:
|
|
||||||
loadBalancer:
|
|
||||||
servers:
|
|
||||||
- url: "http://127.0.0.1:8081"
|
|
||||||
|
|
||||||
### 5) Smoke via Traefik (entrée réelle)
|
|
||||||
|
|
||||||
curl -sS -H 'Host: archicratie.trans-hands.synology.me' http://127.0.0.1:18080/ | head -n 20
|
|
||||||
|
|
||||||
Attendu :
|
|
||||||
|
|
||||||
si non loggé : 302 vers Authelia
|
|
||||||
|
|
||||||
si loggé : HTML du site
|
|
||||||
|
|
||||||
### 6) Piège classique : conflit de nom de conteneur
|
|
||||||
|
|
||||||
Si :
|
|
||||||
Conflict. The container name "/archicratie-web-blue" is already in use...
|
|
||||||
|
|
||||||
Faire :
|
|
||||||
|
|
||||||
sudo docker rm -f archicratie-web-blue
|
|
||||||
sudo env DOCKER_API_VERSION=1.43 docker compose -f docker-compose.yml up -d --force-recreate --no-build web_blue
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
# Runbook — Gitea : Branches, PR, Merge (sans se faire piéger)
|
|
||||||
|
|
||||||
## Règle n°1 (hyper importante)
|
|
||||||
Une PR n’apparaît dans Gitea que si la branche contient **au moins 1 commit différent de `main`**.
|
|
||||||
|
|
||||||
Symptôme typique :
|
|
||||||
- `git push -u origin fix/xxx`
|
|
||||||
- et tu vois : `Total 0 ...`
|
|
||||||
→ ça veut dire : **aucun nouveau commit** → la branche est identique à main → pas de vraie PR à proposer.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Workflow “propre” (pas à pas)
|
|
||||||
### 1) Remettre `main` propre
|
|
||||||
en bash
|
|
||||||
|
|
||||||
git checkout main
|
|
||||||
git pull --ff-only
|
|
||||||
|
|
||||||
### 2) Créer une branche de travail
|
|
||||||
|
|
||||||
git checkout -b fix/mon-fix
|
|
||||||
|
|
||||||
### 3) Faire un changement réel
|
|
||||||
|
|
||||||
Modifier le fichier (ex : src/layouts/EditionLayout.astro)
|
|
||||||
|
|
||||||
Vérifier :
|
|
||||||
|
|
||||||
git status -sb
|
|
||||||
|
|
||||||
→ doit montrer un fichier modifié.
|
|
||||||
|
|
||||||
### 4) Tester
|
|
||||||
|
|
||||||
npm test
|
|
||||||
|
|
||||||
### 5) Commit
|
|
||||||
|
|
||||||
git add src/layouts/EditionLayout.astro
|
|
||||||
git commit -m "Fix: ..."
|
|
||||||
|
|
||||||
### 6) Push
|
|
||||||
|
|
||||||
git push -u origin fix/mon-fix
|
|
||||||
|
|
||||||
### 7) Créer la PR dans l’UI Gitea
|
|
||||||
|
|
||||||
# Aller dans Pull Requests
|
|
||||||
|
|
||||||
# New Pull Request
|
|
||||||
|
|
||||||
Base : main
|
|
||||||
|
|
||||||
Compare : fix/mon-fix
|
|
||||||
|
|
||||||
Branch protection (si “Not allowed to push to protected branch main”)
|
|
||||||
|
|
||||||
# C’est normal si main est protégé :
|
|
||||||
|
|
||||||
On ne pousse jamais directement sur main.
|
|
||||||
|
|
||||||
On merge via PR (UI), avec un compte autorisé.
|
|
||||||
|
|
||||||
Si Gitea refuse de merger automatiquement :
|
|
||||||
|
|
||||||
soit tu actives le réglage côté Gitea “manual merge detection” (admin),
|
|
||||||
|
|
||||||
soit tu fais le merge localement MAIS tu ne pourras pas pousser sur main si la protection l’interdit.
|
|
||||||
|
|
||||||
Conclusion : la voie “pro” = PR + merge UI.
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
# Runbook — Bouton “Proposer” (site → Gitea issue) + Gate Authelia
|
|
||||||
|
|
||||||
## Objectif
|
|
||||||
Permettre une proposition de correction éditoriale depuis un paragraphe du site, en créant une *issue* Gitea pré-remplie, uniquement pour les membres du groupe `editors`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pré-requis
|
|
||||||
- Traefik (edge) en front
|
|
||||||
- Authelia (forwardAuth) opérationnel
|
|
||||||
- Router `/_auth/whoami` exposé (whoami)
|
|
||||||
- Variables `PUBLIC_GITEA_*` injectées au build du site
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Vérification rapide (navigateur)
|
|
||||||
### 1) Qui suis-je ? (groupes)
|
|
||||||
Dans la console :
|
|
||||||
en js :
|
|
||||||
await fetch("/_auth/whoami?_=" + Date.now(), {
|
|
||||||
credentials: "include",
|
|
||||||
cache: "no-store",
|
|
||||||
}).then(r => r.text());
|
|
||||||
|
|
||||||
Attendu (extraits) :
|
|
||||||
|
|
||||||
Remote-User: <login>
|
|
||||||
|
|
||||||
Remote-Groups: ...,editors,... pour un éditeur
|
|
||||||
|
|
||||||
### 2) Le bouton existe ?
|
|
||||||
document.querySelectorAll(".para-propose").length
|
|
||||||
|
|
||||||
> 0 si editors
|
|
||||||
|
|
||||||
0 si non-editor
|
|
||||||
|
|
||||||
## Vérification côté NAS (build vars)
|
|
||||||
### 1) Blue et Green contiennent les constantes ?
|
|
||||||
|
|
||||||
P="/archicratie/archicrat-ia/chapitre-4/"
|
|
||||||
|
|
||||||
curl -sS "http://127.0.0.1:8081$P" | grep -n "const GITEA_BASE" | head -n 2
|
|
||||||
curl -sS "http://127.0.0.1:8082$P" | grep -n "const GITEA_BASE" | head -n 2
|
|
||||||
|
|
||||||
### 2) Si une des deux est vide → rebuild propre
|
|
||||||
|
|
||||||
Dans /volume2/docker/archicratie-web/current :
|
|
||||||
|
|
||||||
cat > .env <<'EOF'
|
|
||||||
PUBLIC_GITEA_BASE=https://gitea.archicratie.trans-hands.synology.me
|
|
||||||
PUBLIC_GITEA_OWNER=Archicratia
|
|
||||||
PUBLIC_GITEA_REPO=archicratie-edition
|
|
||||||
EOF
|
|
||||||
|
|
||||||
sudo env DOCKER_API_VERSION=1.43 docker compose -f docker-compose.yml build --no-cache web_blue web_green
|
|
||||||
sudo env DOCKER_API_VERSION=1.43 docker compose -f docker-compose.yml up -d --force-recreate --no-build web_blue web_green
|
|
||||||
|
|
||||||
## Dépannage (si Proposer “disparaît”)
|
|
||||||
|
|
||||||
Vérifier groupes via /_auth/whoami
|
|
||||||
|
|
||||||
Vérifier const GITEA_BASE via curl sur le slot actif
|
|
||||||
|
|
||||||
Vérifier que Traefik sert bien le slot actif (grep via curl -H Host: ... http://127.0.0.1:18080/...)
|
|
||||||
|
|
||||||
Ouvrir la console : vérifier qu’aucune erreur JS n’empêche l’injection des outils paragraphe
|
|
||||||
@@ -416,25 +416,21 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
|
|||||||
groups,
|
groups,
|
||||||
raw: text,
|
raw: text,
|
||||||
};
|
};
|
||||||
})().catch((err) => {
|
})().catch(() => ({
|
||||||
console.warn("[proposer] whoami fetch failed", err);
|
ok: false,
|
||||||
return {
|
user: "",
|
||||||
ok: false,
|
name: "",
|
||||||
user: "",
|
email: "",
|
||||||
name: "",
|
groups: [],
|
||||||
email: "",
|
raw: "",
|
||||||
groups: [],
|
}));
|
||||||
raw: "",
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return _authInfoPromise;
|
return _authInfoPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Promise unique : est-on editor ?
|
// Promise unique : est-on editor ?
|
||||||
// ⚠️ On reste fail-closed, mais NON destructif (on ne supprime pas sur erreur réseau)
|
|
||||||
const isEditorP = giteaReady
|
const isEditorP = giteaReady
|
||||||
? getAuthInfo().then((info) => info.groups.includes(PROPOSE_REQUIRED_GROUP))
|
? getAuthInfo().then((info) => info.groups.includes(PROPOSE_REQUIRED_GROUP)).catch(() => false)
|
||||||
: Promise.resolve(false);
|
: Promise.resolve(false);
|
||||||
|
|
||||||
const quoteBlock = (s) =>
|
const quoteBlock = (s) =>
|
||||||
@@ -505,24 +501,6 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
|
|||||||
// ==========================
|
// ==========================
|
||||||
const paras = Array.from(document.querySelectorAll('.reading p[id^="p-"]'));
|
const paras = Array.from(document.querySelectorAll('.reading p[id^="p-"]'));
|
||||||
|
|
||||||
// Petit helper : fail-closed mais réversible (hide ≠ remove)
|
|
||||||
function hidePropose(el) {
|
|
||||||
try {
|
|
||||||
el.hidden = true;
|
|
||||||
el.style.display = "none";
|
|
||||||
el.setAttribute("aria-hidden", "true");
|
|
||||||
el.setAttribute("tabindex", "-1");
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
function showPropose(el) {
|
|
||||||
try {
|
|
||||||
el.hidden = false;
|
|
||||||
el.style.display = "";
|
|
||||||
el.removeAttribute("aria-hidden");
|
|
||||||
el.removeAttribute("tabindex");
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const p of paras) {
|
for (const p of paras) {
|
||||||
if (p.querySelector(".para-tools")) continue;
|
if (p.querySelector(".para-tools")) continue;
|
||||||
|
|
||||||
@@ -569,16 +547,16 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
|
|||||||
propose.textContent = "Proposer";
|
propose.textContent = "Proposer";
|
||||||
propose.setAttribute("aria-label", "Proposer une correction sur Gitea");
|
propose.setAttribute("aria-label", "Proposer une correction sur Gitea");
|
||||||
|
|
||||||
// ✅ fail-closed (mais NON destructif)
|
// ✅ fail-closed DUR : inline-style (ne peut pas être overridé par ton CSS)
|
||||||
|
propose.style.display = "none";
|
||||||
propose.dataset.requiresGroup = PROPOSE_REQUIRED_GROUP;
|
propose.dataset.requiresGroup = PROPOSE_REQUIRED_GROUP;
|
||||||
hidePropose(propose);
|
|
||||||
|
|
||||||
const raw = (p.textContent || "").trim().replace(/\s+/g, " ");
|
const raw = (p.textContent || "").trim().replace(/\s+/g, " ");
|
||||||
const excerpt = raw.length > 420 ? (raw.slice(0, 420) + "…") : raw;
|
const excerpt = raw.length > 420 ? (raw.slice(0, 420) + "…") : raw;
|
||||||
|
|
||||||
const issueUrl = buildIssueURL(p.id, raw, excerpt);
|
const issueUrl = buildIssueURL(p.id, raw, excerpt);
|
||||||
|
|
||||||
// Lien fallback (si JS modal casse totalement)
|
// Lien fallback (si JS casse totalement)
|
||||||
propose.href = issueUrl;
|
propose.href = issueUrl;
|
||||||
|
|
||||||
// ✅ Marqueurs pour ProposeModal (interception 2 étapes)
|
// ✅ Marqueurs pour ProposeModal (interception 2 étapes)
|
||||||
@@ -622,18 +600,16 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ✅ Après insertion : on autorise Proposer seulement si groupe editors
|
// ✅ Après insertion : on autorise Proposer seulement si groupe editors
|
||||||
// - ok=false => remove (pas d’UI “Proposer” pour les non-éditeurs)
|
|
||||||
// - erreur fetch => on garde HIDDEN (non destructif) ; un reload pourra réussir
|
|
||||||
if (giteaReady) {
|
if (giteaReady) {
|
||||||
isEditorP.then((ok) => {
|
isEditorP.then((ok) => {
|
||||||
const els = document.querySelectorAll(".para-propose");
|
const els = document.querySelectorAll(".para-propose");
|
||||||
for (const el of els) {
|
for (const el of els) {
|
||||||
if (ok) showPropose(el);
|
if (ok) el.style.display = "";
|
||||||
else el.remove();
|
else el.remove();
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch(() => {
|
||||||
console.warn("[proposer] gate failed; keeping Proposer hidden", err);
|
// fail-closed
|
||||||
document.querySelectorAll(".para-propose").forEach((el) => hidePropose(el));
|
document.querySelectorAll(".para-propose").forEach((el) => el.remove());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user