blog.vfg.lol

Publier et deployer le blog sans CI : le guide local-first

La CI est desactivee — voici l'unique methode pour publier un article et le mettre en ligne sur blog.vfg.lol

TL;DR

Depuis le 2026-07-08, la CI GitHub Actions est désactivée sur le dépôt du blog (RAPPORT-EXPERIENTIEL). Il n'y a plus aucun repli automatique : le chemin local-first est le seul moyen de publier et de déployer. Publier = un appel HTTP à l'API admin (ou l'outil MCP), qui écrit le .md et l'horodate en heure Europe/Paris. Déployer = npm run build:public puis un rollout statique direct vers le VPS via SSH, suivi d'une auto-réparation des permissions. Ce guide donne les étapes exactes, avec exemples copiables.

1. Pourquoi ce changement

L'ancien pipeline reposait sur deux béquilles fragiles, toutes deux supprimées :

  • La CI auto-commit / auto-deploy : un push déclenchait un workflow qui régénérait l'index et déployait. Elle est désactivée entièrement — ne comptez plus dessus, ne la présentez plus comme une option.
  • L'ancien execSync("node build-index.mjs") synchrone dans le chemin de publication MCP : bloquant, sensible aux timeouts, et il « perdait » silencieusement des articles (écrits en draft sur disque mais jamais marqués pub si le matching échouait).

À la place, un module partagé unique scripts/lib/publish-article.mjs (fonction publishArticle) sert les deux points d'entrée (API admin + MCP). Il applique systématiquement le garde-fou d'horodatage scripts/lib/publish-time.mjs (heure civile Europe/Paris, refus de toute date dans le futur). Publier n'exige plus aucun git commit/git push pour exister.

2. Publier un article — API admin (POST :5220)

Prérequis

Depuis le dépôt canonique rapport-experentiel/, lancer les deux serveurs locaux :

# Serveur de lecture (:5195) — sert GET /api/articles
$env:PORT="5195"; $env:HOST="127.0.0.1"; node src/server.js

# Serveur admin (:5220) — expose POST /admin/api/articles (Basic auth)
$env:ADMIN_PASSWORD="<ton-mot-de-passe>"; node admin/server.mjs

Important : lance-les depuis rapport-experentiel/ (le dépôt canonique). POSTS_DIR est résolu par rapport à l'emplacement du fichier serveur — un lancement depuis un autre clone écrirait dans le mauvais dossier.

L'appel de publication

Corps JSON accepté : cat, file (optionnel — sinon nom auto AAAA-MM-JJ-HHMM_slug.md), title, subtitle, author, tags, status (pub ou draft), body (Markdown). Auth : Basic admin:$ADMIN_PASSWORD.

En PowerShell :

$pair = "admin:$env:ADMIN_PASSWORD"
$b64  = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($pair))
$payload = @{
  cat      = "workflows-agentiques"
  title    = "Mon nouvel article"
  subtitle = "Sous-titre optionnel"
  author   = "cursor-agent"
  tags     = @("workflows-agentiques","demo")
  status   = "pub"
  body     = "## Section`n`nLe contenu Markdown de l'article."
} | ConvertTo-Json
Invoke-RestMethod -Uri "http://127.0.0.1:5220/admin/api/articles" `
  -Method Post -ContentType "application/json" `
  -Headers @{ Authorization = "Basic $b64" } -Body $payload

Équivalent curl :

curl -u "admin:$ADMIN_PASSWORD" -H "Content-Type: application/json" \
  -X POST http://127.0.0.1:5220/admin/api/articles \
  -d '{"cat":"workflows-agentiques","title":"Mon nouvel article","author":"cursor-agent","tags":["demo"],"status":"pub","body":"## Section\n\nContenu."}'

La réponse renvoie { success: true, article: { url, file, cat, date, time, ... } }. La date et l'heure sont posées par le garde-fou — ne les fabrique pas à la main.

Vérification de relecture (GET :5195)

$r = Invoke-RestMethod "http://127.0.0.1:5195/api/articles"
$r.articles | Where-Object { $_.url -match "mon-nouvel-article" } | Select-Object url, date, time

Si l'article ressort ici, l'écriture local-first a réussi.

3. Publier un article — outil MCP vfg_commands_devblog_publish

L'outil MCP génère l'article depuis un sujet, l'enregistre dans RAPPORT-EXPERIENTIEL et (option) le publie via le même backend admin. Arguments : topic (requis), model (défaut claude), auto_publish (défaut false).

{
  "server": "user-vfg-commands",
  "toolName": "vfg_commands_devblog_publish",
  "arguments": { "topic": "Bilan de la migration local-first", "auto_publish": true }
}

Avec auto_publish: true, l'article passe par publishArticle (même garde-fou horodatage, même dossier canonique). Contrôle ensuite avec le GET :5195/api/articles ci-dessus. Aperçu sans publier : vfg_commands_devblog_preview.

4. Déployer sur le VPS — sans CI

Le blog n'est pas un service hotfix enregistré (le catalogue vfg_hotfix_catalog liste IdeaPit, Pulse, VFJah, Gandalf). On n'utilise donc pas vfg_hotfix_deploy : c'est un site statique, on pousse les fichiers directement.

4.1 Construire le site statique

npm run build:public   # regénère dist/ (pages .md.html + index + RSS/Atom)

build:public enchaîne check:publish (garde-fou anti-fuite : aucun chemin local, endpoint interne ou localhost dans dist/).

4.2 Rollout vers le VPS

Chemin recommandé : le script dédié, qui fait build + transfert + restart :

npm run deploy:blog   # = scripts/deploy-blog-vfg-lol.ps1

Le script transfère dist/ vers deploy@<VPS>:/var/www/blog.vfg.lol (rsync s'il est présent, sinon scp) via l'accès ssh vps-vfg-deploy. Chemin manuel équivalent (utile sur Windows sans rsync local) :

tar -czf _dist.tar.gz -C dist .
scp _dist.tar.gz vps-vfg-deploy:/tmp/blog-dist.tar.gz
ssh vps-vfg-deploy "rm -rf /tmp/blog-new && mkdir -p /tmp/blog-new && tar -xzf /tmp/blog-dist.tar.gz -C /tmp/blog-new && rsync -a --delete /tmp/blog-new/ /var/www/blog.vfg.lol/ && rm -rf /tmp/blog-new /tmp/blog-dist.tar.gz"

Le compte deploy possède le docroot : il écrit sans sudo. (deploy n'a pas de sudo NOPASSWD — seul le compte admin vps-vfg l'a.)

4.3 Auto-réparation des permissions (idempotente)

rsync -a fixe le groupe sur celui de la source ; on rétablit ensuite l'invariant deploy:www-data + bit setgid pour que nginx lise l'arborescence et que les futurs fichiers héritent du bon groupe :

ssh vps-vfg "sudo chown -R deploy:www-data /var/www/blog.vfg.lol && sudo find /var/www/blog.vfg.lol -type d -exec chmod 2775 {} + && sudo find /var/www/blog.vfg.lol -type f -exec chmod 664 {} +"

4.4 Service et reverse-proxy

Les fichiers sont servis par le conteneur nginx vfg-fleet-blog-static-1 sur 127.0.0.1:8092, exposé en HTTPS par Caddy (systemd) sur blog.vfg.lol. Site statique = aucun redémarrage nécessaire : nginx sert les fichiers à chaud.

4.5 Contrôle de santé

# En local sur le VPS (nginx direct)
ssh vps-vfg-deploy "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8092/"
# Depuis l'extérieur (chaîne Caddy complète)
Invoke-WebRequest https://blog.vfg.lol/ -UseBasicParsing | Select-Object StatusCode

Un 200 des deux côtés = déploiement en ligne. L'URL d'un article suit https://blog.vfg.lol/devblog/posts/<cat>/<fichier>.md.html.

5. Vérification par la gate / le MCP

Outils MCP en lecture seule, utiles pour confirmer l'état de la flotte :

  • vfg_gate_health — santé de VFGate.
  • vfg_health — santé du bridge VFGate (/api/bridge/v1/health).
  • vfg_hotfix_verify ({ service }) — vérifie healthz local/public d'un service hotfix. Le blog n'en étant pas un, on s'appuie sur le curl/Invoke-WebRequest du §4.5 pour lui.

6. Dépannage

  • 401 sur POST :5220 : ADMIN_PASSWORD non chargé ou différent. Vérifie l'env du process serveur ; l'en-tête doit être Basic base64(admin:MOT_DE_PASSE).
  • L'article n'apparaît pas dans GET :5195 : serveur lancé depuis le mauvais clone. Vérifie POSTS_DIR (node -e "import('./scripts/lib/publish-article.mjs').then(m=>console.log(m.POSTS_DIR))") — il doit pointer sur rapport-experentiel/devblog/posts.
  • Horodatage « corrigé » en console : normal — le garde-fou refuse une date future ou complète une heure manquante avec l'heure réelle de Paris. Ne force pas de date.
  • check:publish échoue : une fuite (chemin local, endpoint interne, localhost) a été détectée dans dist/. Corrige la source .md puis relance build:public.
  • Site non mis à jour après déploiement : re-vérifie que rsync --delete a bien tourné et que le curl sur :8092 renvoie la bonne taille ; puis rejoue l'auto-réparation des permissions du §4.3.
  • Permission refusée sur le VPS : deploy possède le docroot (écriture OK sans sudo) ; les chown/chmod passent par le compte admin vps-vfg (sudo NOPASSWD). N'inverse pas les deux.

source markdown

Concepts de cet article

MCPVPSVFGategateIdeaPitPulseVFJah MessengerCaddy

Partage le plus de concepts avec