Compare commits

..

1 Commits

Author SHA1 Message Date
cd4ce80ac1 docs(ops): add triple-source sync + troubleshooting + proposer spec
All checks were successful
CI / build-and-anchors (push) Successful in 1m46s
SMOKE / smoke (push) Successful in 11s
2026-02-01 14:38:24 +01:00
15 changed files with 85 additions and 200 deletions

3
.env Normal file
View File

@@ -0,0 +1,3 @@
PUBLIC_GITEA_BASE=https://gitea.archicratie.trans-hands.synology.me
PUBLIC_GITEA_OWNER=Archicratia
PUBLIC_GITEA_REPO=archicratie-edition

4
.env.development Normal file
View File

@@ -0,0 +1,4 @@
PUBLIC_GITEA_BASE=https://gitea.archicratie.trans-hands.synology.me
PUBLIC_GITEA_OWNER=Archicratia
PUBLIC_GITEA_REPO=archicratie-edition
PUBLIC_SITE=https://archicratie.trans-hands.synology.me

5
.env.local Normal file
View File

@@ -0,0 +1,5 @@
FORGE_API=http://192.168.1.20:3000
FORGE_BASE=https://gitea.archicratie.trans-hands.synology.me
FORGE_TOKEN=aW73wpfJ4MiN2!3UU69qL*vWF9$9V7f@2
PUBLIC_GITEA_OWNER=Archicratia
PUBLIC_GITEA_REPO=archicratie-edition

6
.env.production Normal file
View File

@@ -0,0 +1,6 @@
PUBLIC_SITE=https://archicratie.trans-hands.synology.me
PUBLIC_RELEASE=0.1.0
PUBLIC_GITEA_BASE=https://gitea.archicratie.trans-hands.synology.me
PUBLIC_GITEA_OWNER=Archicratia
PUBLIC_GITEA_REPO=archicratie-edition

View File

@@ -10,15 +10,6 @@ Si un seul de ces 3 paramètres est faux → on obtient :
- 404 / redirect login inattendu
- ou un repo/owner incorrect
# Diagnostic — “Proposer” (résumé)
**Symptôme :** clic “Proposer” → 404 / login / mauvais repo
**Cause la plus fréquente :** `PUBLIC_GITEA_OWNER` (casse sensible) ou `PUBLIC_GITEA_REPO` faux.
➡️ Procédure complète (pas-à-pas + commandes) : voir `docs/TROUBLESHOOTING.md#proposer-404`.
## 1) Variables utilisées (publique, côté build Astro)
- `PUBLIC_GITEA_BASE`

View File

@@ -1,15 +1,5 @@
# Déploiement production (Synology DS220+ / DSM 7.3) — Astro → Nginx statique
> ✅ **CANONIQUE** — Procédure de référence “prod DS220+ / DSM 7.3”.
> Toute modif de déploiement doit être faite **ici**, via PR sur Gitea/main (pas dédition à la main en prod).
> Périmètre : build Docker (Node→Nginx), blue/green 8081/8082, Reverse Proxy DSM, smoke, rollback.
> Dépendances critiques : variables PUBLIC_GITEA_* (sinon “Proposer” part en 404/login loop).
> Voir aussi : OPS-REFERENCE.md (index), OPS_COCKPIT.md (checklist), TROUBLESHOOTING.md (incidents).
Dernière mise à jour : 2026-02-01
Ce document décrit une mise en place stable sur NAS :
@@ -83,17 +73,6 @@ for f in .env .env.local .env.production .env.production.local; do
[ -f "$f" ] && echo "---- $f" && grep -nE '^PUBLIC_GITEA_(BASE|OWNER|REPO)=' "$f" || true
done
En cas déchec :
- 404 / login loop / mauvais repo → `docs/TROUBLESHOOTING.md#proposer-404`
- double onglet → `docs/TROUBLESHOOTING.md#proposer-double-onglet`
## Diagnostic — “Proposer” (résumé)
**Symptôme :** clic “Proposer” → 404 / login / mauvais repo
**Cause la plus fréquente :** `PUBLIC_GITEA_OWNER` (casse sensible) ou `PUBLIC_GITEA_REPO` faux.
➡️ Procédure complète (pas-à-pas + commandes) : voir `docs/TROUBLESHOOTING.md#proposer-404`.
## 5) Reverse Proxy DSM (le point clé)
### DSM 7.3 :

View File

@@ -43,15 +43,61 @@ Le flow ne doit jamais ouvrir deux onglets.
- un seul `a.target="_blank"` (ou équivalent) déclenché
- sur click : handler doit neutraliser les propagations parasites
## Diagnostic (canonique)
### Vérification (NAS)
en sh :
curl -fsS http://127.0.0.1:8082/archicratie/archicrat-ia/chapitre-4/ > /tmp/page.html
grep -n "window.open" /tmp/page.html | head
Le diagnostic détaillé est centralisé dans `docs/TROUBLESHOOTING.md` pour éviter les doublons.
Doit retourner 0 ligne.
- 404 / non autorisé / redirect login :
- voir : `TROUBLESHOOTING.md#proposer-404`
- cause la plus fréquente : `PUBLIC_GITEA_OWNER/REPO` faux (souvent casse)
## 3) Diagnostic “trace ouverture onglet” (navigateur)
- Double onglet :
- voir : `TROUBLESHOOTING.md#proposer-double-onglet`
- cause la plus fréquente : double handler (bubbling) ou `window.open` + `a.click()`
Dans la console, tu peux surcharger temporairement les mécanismes douverture pour tracer :
si un window.open survient,
ou si un a.click target _blank est appelé.
But : prouver quil ny a quun seul événement douverture.
## 4) URL attendue (forme)
Longlet doit ressembler à :
{PUBLIC_GITEA_BASE}/{OWNER}/{REPO}/issues/new?title=...&body=...
Important : owner et repo doivent être exactement ceux du repo canonique.
## 5) Pré-requis daccès
Lutilisateur doit être loggé sur Gitea pour accéder à /issues/new
Si non loggé : redirect vers /user/login (comportement normal)
## 6) Tests fonctionnels (checklist)
Ouvrir une page chapitre (ex chapitre 4)
Clic Proposer (sur un paragraphe)
Choix 1 puis choix 2
Vérifier :
1 seul onglet
URL du repo correct
formulaire new issue visible
title/body pré-remplis (chemin + ancre + texte actuel)
Créer lissue → vérifier le traitement CI/runner
## 7) Pannes typiques + causes
404 sur issue/new : PUBLIC_GITEA_OWNER/REPO faux (souvent casse)
2 onglets : double handler (bubbling + ouverture multiple)
pas de favicon : cache ou absence dans public/ → rebuild

View File

@@ -1,46 +1,6 @@
# OPS — Déploiement Archicratie Web Edition (Mac Studio → DS220+)
OPS — Déploiement Archicratie Web Edition (Mac Studio → DS220+)
Objectif : déployer une nouvelle version du site sur le NAS (DS220+) sans jamais casser la prod, en utilisant un schéma blue/green piloté par DSM Reverse Proxy, avec une procédure robuste même quand docker compose build est instable sur le NAS.
> 🟧 **LEGACY / HISTORIQUE** — Ce document nest plus la source de vérité.
> Référence actuelle : docs/DEPLOY_PROD_SYNOLOGY_DS220.md (canonique).
> Statut : gelé (on nédite plus que pour ajouter un lien vers le canonique, si nécessaire).
> Raison : doublon → risque de divergence → risque derreur en prod.
> Si tu lis ceci pour déployer : stop → ouvre le canonique.
> ⚠️ LEGACY — Ne pas suivre pour déployer.
> Doc conservé pour historique.
> Canon : `DEPLOY_PROD_SYNOLOGY_DS220.md` + `OPS-SYNC-TRIPLE-SOURCE.md`.
## Pourquoi ce doc existe encore
- Historique : il capture des repères (domaines, ports, logique blue/green) tels quils ont été consolidés pendant la phase dimplémentation.
- Sécurité : éviter la divergence documentaire (un seul pas-à-pas officiel).
- Maintenance : si tu dois déployer, tu suis le canonique ; ici tu ne viens que pour comprendre “doù ça vient”.
## Ce quil faut faire aujourdhui (canonique)
➡️ Déploiement = `docs/DEPLOY_PROD_SYNOLOGY_DS220.md` (procédure détaillée, à jour).
## Schéma (résumé, sans commandes)
- Ne jamais toucher au slot live.
- Construire/tester sur lautre slot.
- Smoke test.
- Bascule DSM Reverse Proxy (8081 ↔ 8082).
- Rollback DSM si besoin.
<details>
> 🚫 NE PAS UTILISER POUR PROD — ARCHIVE UNIQUEMENT
<summary>Archive — ancien pas-à-pas (NE PAS SUIVRE)</summary>
> ⚠️ Archive. Ce contenu est conservé pour mémoire.
## 0) Repères essentiels
Noms & domaines
• Site public (prod) : https://archicratie.trans-hands.synology.me
@@ -423,5 +383,3 @@ Fix standard (dans le vrai dossier site/) :
rm -rf node_modules .astro dist
npm ci
npm run dev
</details>

View File

@@ -4,7 +4,7 @@ Document “pivot” : liens, invariants, conventions, commandes réflexes.
## 0) Invariants (à ne pas casser)
- **Source de vérité Git** : origin/main (repo Archicratia/archicratie-edition sur Gitea).
- **Source de vérité Git** : `origin/main` sur :contentReference[oaicite:0]{index=0}.
- **Prod** : conteneur `archicratie-web-*` (nginx) derrière reverse proxy DSM.
- **Config “Proposer”** : dépend de `PUBLIC_GITEA_BASE`, `PUBLIC_GITEA_OWNER`, `PUBLIC_GITEA_REPO` injectés au build.
- **Branches** : `main` = travail ; `master` = legacy/compat (alignée mais protégée).

View File

@@ -1,15 +1,5 @@
# OPS Runbook — Archicratie Web (NAS Synology DS220 + Gitea)
> 🟦 **ALIAS (résumé)** — Runbook 1-page pour opérer vite sans se tromper.
> La procédure détaillée **canonique** est : docs/DEPLOY_PROD_SYNOLOGY_DS220.md.
> Source Git : Gitea/main ; déploiement = rebuild depuis main ; pas de hotfix non versionné.
> Déploiement : build sur slot inactif → smoke → bascule DSM → rollback si besoin.
> Incidents connus : voir docs/TROUBLESHOOTING.md.
## 0. Objectif
Ce document décrit la procédure **exacte** pour :
- maintenir un état cohérent entre **Local (Mac Studio)**, **Gitea**, **NAS (prod)** ;

View File

@@ -71,7 +71,8 @@ Ce document décrit la synchronisation **sans ambiguïté** entre :
### Étape B — NAS : aligner `current` sur `origin/main`
Sur NAS, git nest pas forcément installé : on utilise un conteneur git.
en sh :
en sh:
APP="/volume2/docker/archicratie-web/current"
U_ID="$(id -u)"; G_ID="$(id -g)"

View File

@@ -15,7 +15,6 @@ Toujours isoler : **Local**, **Gitea**, **NAS**, **Navigateur**.
---
<a id="proposer-404"></a>
## 1) “Proposer” ouvre Gitea mais retourne 404 / non autorisé
### Symptôme
@@ -39,7 +38,6 @@ Puis rebuild + restart du container + smoke.
---
<a id="proposer-double-onglet"></a>
## 2) Double onglet à la validation du flow “Proposer”
### Symptôme
@@ -60,15 +58,13 @@ en sh :
curl -fsS http://127.0.0.1:8082/archicratie/archicrat-ia/chapitre-4/ > /tmp/page.html
grep -n "window.open" /tmp/page.html | head
Fix
# Fix
garder un seul mécanisme douverture
sur click : preventDefault() + stopImmediatePropagation()
<a id="Favicon-504-erreurs"></a>
## 3) Favicon 504 / erreurs console sur favicon
# Symptôme
Console navigateur : GET /favicon.ico 504
@@ -105,7 +101,6 @@ git fetch impossible sur le NAS.
Git non installé sur DSM shell.
# Fix standard (recommandé)
Utiliser un conteneur git :
APP="/volume2/docker/archicratie-web/current"
@@ -181,9 +176,9 @@ BuildKit is enabled but the buildx component is missing
client version ... too new. Maximum supported API version ...
# Fix “robuste” (principe)
Fix “robuste” (principe)
installer buildx si nécessaire
# installer buildx si nécessaire
si DSM/docker API ancienne : définir DOCKER_API_VERSION=<compatible> (selon ton environnement)

View File

@@ -63,7 +63,7 @@ Si lID exact nexiste plus :
But : éviter les “liens morts” historiques quand une régénération dIDs a eu lieu.
Limite : cest un fallback de dernier recours (moins déterministe quun alias explicite).
Le mécanisme recommandé reste : `src/anchors/anchor-aliases.json` + injection au build.
Le mécanisme recommandé reste : `docs/anchor-aliases.json` + injection au build.
_______________________________________
@@ -87,7 +87,7 @@ Les IDs dancres générés (ou dérivés) peuvent changer :
## 2) Le mapping dalias
- Fichier versionné (ex) : `src/anchors/anchor-aliases.json`
- Fichier versionné (ex) : `docs/anchor-aliases.json`
- Format : `oldId -> newId` par page
Ex en json :

View File

@@ -379,64 +379,6 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
const FULL_TEXT_SOFT_LIMIT = 1600;
const URL_HARD_LIMIT = 6500;
// ✅ Gate par groupe (Traefik→Authelia→LLDAP via /_auth/whoami)
const WHOAMI_PATH = "/_auth/whoami";
const PROPOSE_REQUIRED_GROUP = "editors";
let _authInfoPromise = null;
function parseWhoamiLine(text, key) {
const re = new RegExp(`^${key}:\\s*(.*)$`, "mi");
const m = String(text || "").match(re);
return (m?.[1] ?? "").trim();
}
async function getAuthInfo() {
if (_authInfoPromise) return _authInfoPromise;
_authInfoPromise = (async () => {
const res = await fetch(`${WHOAMI_PATH}?_=${Date.now()}`, {
credentials: "include",
cache: "no-store",
redirect: "follow",
});
const text = await res.text().catch(() => "");
const groups = parseWhoamiLine(text, "Remote-Groups")
.split(",")
.map((s) => s.trim())
.filter(Boolean);
return {
ok: Boolean(res && res.ok),
user: parseWhoamiLine(text, "Remote-User"),
name: parseWhoamiLine(text, "Remote-Name"),
email: parseWhoamiLine(text, "Remote-Email"),
groups,
raw: text,
};
})().catch((err) => {
console.warn("[proposer] whoami fetch failed", err);
return {
ok: false,
user: "",
name: "",
email: "",
groups: [],
raw: "",
};
});
return _authInfoPromise;
}
// Promise unique : est-on editor ?
// ⚠️ On reste fail-closed, mais NON destructif (on ne supprime pas sur erreur réseau)
const isEditorP = giteaReady
? getAuthInfo().then((info) => info.groups.includes(PROPOSE_REQUIRED_GROUP))
: Promise.resolve(false);
const quoteBlock = (s) =>
String(s || "")
.split(/\r?\n/)
@@ -505,24 +447,6 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
// ==========================
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) {
if (p.querySelector(".para-tools")) continue;
@@ -569,16 +493,12 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
propose.textContent = "Proposer";
propose.setAttribute("aria-label", "Proposer une correction sur Gitea");
// ✅ fail-closed (mais NON destructif)
propose.dataset.requiresGroup = PROPOSE_REQUIRED_GROUP;
hidePropose(propose);
const raw = (p.textContent || "").trim().replace(/\s+/g, " ");
const excerpt = raw.length > 420 ? (raw.slice(0, 420) + "…") : raw;
const issueUrl = buildIssueURL(p.id, raw, excerpt);
// Lien fallback (si JS modal casse totalement)
// Lien fallback (si JS casse totalement)
propose.href = issueUrl;
// ✅ Marqueurs pour ProposeModal (interception 2 étapes)
@@ -586,6 +506,9 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
propose.dataset.url = issueUrl;
propose.dataset.full = raw;
// ❌ PAS de target=_blank ici
// ❌ PAS de rel noopener ici
row.appendChild(propose);
}
@@ -621,22 +544,6 @@ const GITEA_REPO = import.meta.env.PUBLIC_GITEA_REPO ?? "";
p.appendChild(tools);
}
// ✅ Après insertion : on autorise Proposer seulement si groupe editors
// - ok=false => remove (pas dUI “Proposer” pour les non-éditeurs)
// - erreur fetch => on garde HIDDEN (non destructif) ; un reload pourra réussir
if (giteaReady) {
isEditorP.then((ok) => {
const els = document.querySelectorAll(".para-propose");
for (const el of els) {
if (ok) showPropose(el);
else el.remove();
}
}).catch((err) => {
console.warn("[proposer] gate failed; keeping Proposer hidden", err);
document.querySelectorAll(".para-propose").forEach((el) => hidePropose(el));
});
}
// Auto-checkpoint
let lastAuto = 0;
function writeLastSeen(id) {