PROCEDIMIENTO — Deploy Blog y Wiki a Cloudflare Pages

Para agentes IA y operador humano. Esta nota describe de forma exhaustiva cómo desplegar los dos sitios web de Sammi De Blas (blog sammideblas.com y wiki doc.sammideblas.com) a Cloudflare Pages desde GitHub vía GitHub Actions. Un git push a main despliega solo. Tiempo medio de deploy: ~60 segundos.

Origen: documentación canónica tras la unificación de deploy realizada el 2026-07-06. Cualquier divergencia entre esta nota y el estado real ⊂ avisar y reescribir.


0. TL;DR (resumen para agent que solo quiere ejecutar)

BlogWiki
Dominio públicosammideblas.com (+ blog.sammideblas.com, www.sammideblas.com)doc.sammideblas.com
Repo GitHubPoisonXploIT/blog (privado)PoisonXploIT/wiki (privado)
CF Pages projectblogsammideblas-wiki
CF Pages project IDbdba5fc0-9e9a-4d5f-b9bb-2d92ec75455118d6cbcc-89a6-44ba-985e-7a9fa128707d
pages.dev subdomainblog-f5e.pages.devsammideblas-wiki.pages.dev
Ruta local principalC:\Users\Sammi De Blas\hermes_data\blog-buildC:\Users\Sammi De Blas\hermes_data\wiki-build
Workflow.github/workflows/deploy.yml.github/workflows/deploy.yml
Deploy manual (en máquina)git push origin maingit push origin main

Cuenta Cloudflare: ID 43c5d4a754f2951ba36185df5deef60c.

Secrets en cada repo GitHub (configurados una sola vez, ver sección 5):

  • CLOUDFLARE_API_TOKEN (valor del API Token CF, scope Account → Cloudflare Pages → Edit)
  • CLOUDFLARE_ACCOUNT_ID = 43c5d4a754f2951ba36185df5deef60c

0.5. Dónde está todo (mapa de localización — para agentes sin contexto)

Antes de hacer nada, saber DÓNDE vive cada cosa es más importante que saber CÓMO se hace. Esta sección es el mapa. Si necesitas X, mira la fila de X y ve a esa ubicación. Si la ubicación no existe en tu máquina, clona/descarga según se indique.

¿Qué necesitas?Dónde estáSi no está en tu máquina
Fuente del blog (Quartz, posts)Repo GitHub privado PoisonXploIT/bloggh repo clone PoisonXploIT/blog blog-build
Fuente del wiki (Quartz, vault público)Repo GitHub privado PoisonXploIT/wikigh repo clone PoisonXploIT/wiki wiki-build
Ruta local por defecto del blog (máquina de referencia)C:\Users\Sammi De Blas\hermes_data\blog-build\clonar arriba a esa ruta
Ruta local por defecto del wikiC:\Users\Sammi De Blas\hermes_data\wiki-build\idem
Vault Obsidian completo (editorial, noteswise)C:\Users\Sammi De Blas\Desktop\OBSIDIAN VAULT\Destino\autosincronizado por Syncthing (no en git)
Esta nota canónica (procedimiento deploy super-detallado, 42 KB)3 copias espejo: (1) Destino\PROCEDIMIENTOS\PROCEDIMIENTO - Deploy Blog y Wiki a Cloudflare Pages.md en el vault Obsidian · (2) .github\DEPLOY.md del repo PoisonXploIT/blog · (3) content\PROCEDIMIENTOS\deploy-blog-wiki.md del repo PoisonXploIT/wikiLa (2) y (3) llegan con git clone; la (1) llega vía Syncthing del vault
Índice general / context pack para agentesDestino\PROCEDIMIENTOS\PROCEDIMIENTO - INDICE GENERAL Agentes IA.md en el vault del EscritorioSyncthing
AGENTS.md (puntero corto para agentes que clone un repo)Raíz de cada repo: PoisonXploIT/blog/AGENTS.md y PoisonXploIT/wiki/AGENTS.mdviene con el git clone
Workflow de deploy (CI/CD).github\workflows\deploy.yml en cada repoviene con el git clone
Secrets de Cloudflare (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID)GitHub Secrets de PoisonXploIT/blog y PoisonXploIT/wiki (no en texto plano en ningún lado)gh secret set CLOUDFLARE_API_TOKEN --repo PoisonXploIT/blog --body "<token>" y análogo en /wiki
Cloudflare Account ID43c5d4a754f2951ba36185df5deef60c (no es secreto, está en dashboard CF)
CF Pages project IDsblog = bdba5fc0-9e9a-4d5f-b9bb-2d92ec754551 · sammideblas-wiki = 18d6cbcc-89a6-44ba-985e-7a9fa128707d
Dominios en producciónBlog: sammideblas.com, blog.sammideblas.com, www.sammideblas.com · Wiki: doc.sammideblas.com
Token Cloudflare real (el valor, no el nombre del secret)Dashboard Cloudflare → My Profile → API Tokens → token blog-wiki-deploycrear uno nuevo (sección 5.1) → fijarlo en los 4 secrets (2 repos × 2 secrets)
Logs históricos de wrangler (del deploy manual viejo pre-Actions)hermes_data\home\.config\.wrangler\logs\ (referencia, no tocar)no son necesarios para operar
HOME de Hermes / agente (sensible: auth.json, .ssh, .env, config.yaml, state.db, sessions/, kanban.db…)C:\Users\Sammi De Blas\hermes_data\ (raíz)nunca se commitea a GitHub; no se migra salvo reinstalación explícita de Hermes
Repos de código propios (Ciber_Radar, sec-dashboard, Gravity-SOC, Auditing_with_PowerShell, etc.)Cada uno en su propio remote PoisonXploIT/<name>gh repo clone PoisonXploIT/<name>
Subdominio sec.sammideblas.comPoisonXploIT/sec-dashboard (no en CF Pages; pendiente confirmar hosting)

Punto de arranque mínimo para un agente en máquina limpia

# 0) Instalar: git, Node 22+, npm 10.9+, gh CLI
gh auth login                                  # autenticar en navegador
gh auth refresh -h github.com -s workflow      # solo si vas a editar .github/workflows/*
 
# 1) Clonar los dos repos que importan para el deploy:
gh repo clone PoisonXploIT/blog   <ruta>/blog-build
gh repo clone PoisonXploIT/wiki   <ruta>/wiki-build
 
# 2) Abrir y leer (en este orden):
#    <ruta>/blog-build/AGENTS.md           ← puntero corto + reglas
#    <ruta>/blog-build/.github/DEPLOY.md   ← procedimiento canónico completo
#    (o equivalentes en wiki-build: AGENTS.md y content/PROCEDIMIENTOS/deploy-blog-wiki.md)
 
# 3) Para publicar un post:
#    - crea content/posts/0x71-siguiente.md (plantilla en DEPLOY.md sec 4.1)
#    - git add; git commit -m "post: 0x71 ..."; git push origin main
#    - gh run list --repo PoisonXploIT/blog --limit 1    (verificar success)

A partir de ese punto no necesitas contexto adicional. Todo el procedimiento (frontmatter, plantilla, crear token CF si caduca, permisos OAuth, troubleshooting) está en el procedimiento canónico que acabas de clonar.


1. Arquitectura general

┌─────────────────────────────────────────────────────────────────┐
│  Máquina local (Windows / Linux)                                 │
│  ┌───────────────────────────┐    ┌───────────────────────────┐ │
│  │ hermes_data\blog-build    │    │ hermes_data\wiki-build      │ │
│  │ (Quartz, fuente blog)     │    │ (Quartz, fuente wiki)       │ │
│  └─────────────┬─────────────┘    └─────────────┬─────────────┘ │
└────────────────┼────────────────────────────────┼────────────────┘
                 │ git push origin main           │ git push origin main
                 ▼                                ▼
        ┌────────────────────┐           ┌────────────────────┐
        │  GitHub: PoisonXploIT/blog      │  GitHub: PoisonXploIT/wiki       │
        │  .github/workflows/deploy.yml   │  .github/workflows/deploy.yml    │
        └─────────┬──────────┘           └─────────┬──────────┘
                  │ triggers on push to main         │ triggers on push to main
                  ▼                                  ▼
        ┌────────────────────┐           ┌────────────────────┐
        │  GitHub Actions runner          │  GitHub Actions runner            │
        │  - actions/checkout@v4          │  - actions/checkout@v4           │
        │  - actions/setup-node@v4 (22)   │  - actions/setup-node@v4 (22)    │
        │  - npm ci                       │  - npm ci                       │
        │  - npx quartz build             │  - npx quartz build              │
        │  - cloudflare/wrangler-action@v3│  - cloudflare/wrangler-action@v3 │
        └─────────┬──────────┘           └─────────┬──────────┘
                  │ wrangler pages deploy            │ wrangler pages deploy
                  │   public --project-name=blog     │   public --project-name=sammideblas-wiki
                  ▼                                  ▼
        ┌────────────────────┐           ┌────────────────────┐
        │  CF Pages project 'blog'        │  CF Pages project 'sammideblas-wiki'                │
        │  - blog-f5e.pages.dev           │  - sammideblas-wiki.pages.dev                        │
        │  - alias: sammideblas.com       │  - alias: doc.sammideblas.com                        │
        │  - alias: blog.sammideblas.com  │                                                      │
        │  - alias: www.sammideblas.com   │                                                      │
        └────────────────────┘           └────────────────────┘

Componentes

#ComponenteRol
1Repos GitHub privadosFuente de verdad + control de versiones. Privados porque contienen drafts y documentos internos.
2Quartz 4.5.2Generador de sitio estático. Convierte Markdown en HTML en public/. Configuración en quartz.config.ts y quartz.layout.ts.
3GitHub Actions deploy.ymlWorkflow en .github/workflows/deploy.yml. Dispara build + wrangler pages deploy en cada push a main.
4Secrets de GitHubVariables cifradas en el repo: CLOUDFLARE_API_TOKEN y CLOUDFLARE_ACCOUNT_ID. NO se commitean nunca.
5Cloudflare PagesHosting del sitio estático. Cada deployment queda versionado y asociado a un commit hash de GitHub.
6wrangler-action@v3Action oficial de Cloudflare que ejecuta wrangler pages deploy public --project-name=<name> dentro del runner.

Por qué esta arquitectura (motivación)

Antes del 2026-07-06:

  • El blog no tenía repo GitHub (PoisonXploIT/blog devolvía 404).
  • El deploy era manual con npx wrangler pages deploy desde un host Linux en /opt/data/blog-build.
  • El workflow del wiki apuntaba a GitHub Pages (no estaba activado → roto).
  • Una segunda máquina nunca encontraba la fuente ni el flujo de deploy.

Después:

  • Cualquier máquina solo hace git clone y con git push despliega solo.
  • Cualquier agente IA encuentra el procedimiento leyendo AGENTS.md + .github/DEPLOY.md (blog) o content/PROCEDIMIENTOS/deploy-blog-wiki.md (wiki).
  • No hace falta token de Cloudflare local (los secrets viven en GitHub).

2. Prerequisites (lo que necesitas instalado en cada máquina)

Software obligatorio

  • Git >= 2.40 (https://git-scm.com)
  • Node.js >= 22 (verificado con 22.18.0; los workflows exigen 22)
  • npm >= 10.9.2 (viene con Node)
  • GitHub CLI gh autenticada con scope repo + workflow (ver sección 7)

Opcional (para deploys locales sin Actions)

  • wrangler (npm install -g wrangler o npx wrangler@latest) — solo si quieres hacer deploys locales manuales.
  • Un API Token de Cloudflare con scope Account → Cloudflare Pages → Edit (ver sección 5).

Cuentas necesarias

  • Cuenta de GitHub con acceso a los repos PoisonXploIT/blog y PoisonXploIT/wiki.
  • Cuenta de Cloudflare con el proyecto Pages blog y sammideblas-wiki (cada ID en sección 0). Ten solo una cuenta (ID 43c5d4a754f2951ba36185df5deef60c).

Verificar instalacion

git --version
node --version     # debe ser >= 22
npm --version      # >= 10.9.2
gh --version
gh auth status     # debe mostrar "Logged in to github.com account PoisonXploIT"

3. Setup en una máquina nueva (one-time)

# 1. Elegir ubicación (ej. hermes_data\)
mkdir C:\Users\Sammi De Blas\hermes_data
cd C:\Users\Sammi De Blas\hermes_data
 
# 2. Clonar ambos repos (privados; gh pedira auth la primera vez)
gh repo clone PoisonXploIT/blog   blog-build
gh repo clone PoisonXploIT/wiki   wiki-build
 
# 3. Instalar dependencias locales (solo para build local / tests)
cd blog-build ; npm install ; cd ..\wiki-build ; npm install ; cd ..
 
# 4. Verificar estado (debería estar todo verde)
cd blog-build ; git status ; cd ..\wiki-build ; git status

A partir de aquí:

  • Para desplegar tras un cambio: hacer commit + git push origin main. No hace falta wrangler ni token CF local — el deploy lo ejecuta GitHub Actions con los secrets del repo.
  • Para probar localmente antes de pushear: npx quartz build --serve (levanta un preview en http://localhost:8080).

4. Flujo de trabajo diario

4.1 Publicar un post nuevo en el blog

cd C:\Users\Sammi De Blas\hermes_data\blog-build

Paso 1 — crear el archivo Markdown. Slug sugerido: 0xNN-titulo-con-guiones.md dentro de content\posts\. Numeración hexadecimal consecutiva: el último publicado es 0x69 (CySA Domain 3), el más reciente en master es 0x70 (17 days listening to the air). El siguiente debería ser 0x71.

Plantilla frontmatter obligatoria:

---
title: "0xNN: Título del Post"
date: 2026-07-06       # ISO date (YYYY-MM-DD). Opcional: añadir T HH:mm:ss+00:00
draft: false            # false = se publica. true = NO se publica
description: "Una linea de descripción para SEO y preview"
tags:
  - tag1
  - tag2
  - tag3
---
 
# Título del post
 
Contenido en Markdown / HTML embebido...

El script new-post.sh (bash) automatiza la creación del archivo y lo inserta en content/index.md si tienes ”## Posts” como cabecera. Ej: ./new-post.sh "0x70: 17 days listening to the air".

Paso 2 — preview local (opcional pero recomendado):

npx quartz build --serve
# abrir http://localhost:8080
# Ctrl+C para parar

Paso 3 — commit y push:

git add content\posts\0xNN-slug.md
git commit -m "post: 0xNN Título del post"
git push origin main

Paso 4 — verificar el deploy:

gh run list --repo PoisonXploIT/blog --limit 1
# esperar a que el ultimo run ponga "completed / success"
# (suele tardar ~60 segundos)

El post aparece en https://sammideblas.com/posts/0xNN-slug. El sitemap y el RSS (https://sammideblas.com/index.xml) se regeneran automáticamente.

4.2 Publicar un cambio en la wiki

cd C:\Users\Sammi De Blas\hermes_data\wiki-build
# editar el archivo o crear una nota nueva en content\<carpeta>\<nota>.md
git add content\<ruta-modificada>
git commit -m "wiki: descripción del cambio"
git push origin main
gh run list --repo PoisonXploIT/wiki --limit 1
# producción: https://doc.sammideblas.com

Característica importante del wiki: ignorePatterns

El wiki publica solo una parte del vault. En quartz.config.ts hay un ignorePatterns que excluye carpetas/nombres sensibles del build público:

ignorePatterns: [
  "LOGS RAW", "CORPORATIVO", "Templates", "Anexos", ".obsidian",
  ".smart-env", "BMO", "Chats", "Clippings", "Agentes IA", "COPILOT",
  "NOTAS DIARIAS SUELTAS", "PROYECTO MVK2", "Troubleshooting", "TRENDING", "BLOG",
  "000_DASHBOARD_PRINCIPAL.md", "00_CENTRAL_COMMANDS.md",
  "PROCEDIMIENTO*", "PROCEDIMIENTOS*", "*Index*", "brave device log*"
]

Cualquier nota en content/PROCEDIMIENTOS/ se commitea al repo (llega a todas las máquinas) PERO NO se publica en doc.sammideblas.com. That’s by design: es donde viven notas internas como IDs de CF Pages, comandos con tokens, runbooks operativos.

Si creas una carpeta nueva sensible, añádela también a ignorePatterns antes de commitear.

.gitignore del wiki

Estas rutas nunca se commitean:

node_modules/            public/                 .obsidian/
.smart-env/              .DS_Store               quartz/.cache/*.bak
content/LOGS RAW/        content/TRENDING/       content/Agentes IA/
content/COMP TIA CySA+/  content/NOTAS DIARIAS SUELTAS/  content/PROYECTO MVK2/
content/BMO/             content/COPILOT/        content/Chats/
content/Clippings/       content/CORPORATIVO/   content/Troubleshooting/
content/BLOG/            content/000_DASHBOARD_PRINCIPAL.md
content/00_CENTRAL_COMANDOS.md

NOTA: el 2026-07-06 se quitó content/PROCEDIMIENTO* del .gitignore porque queremos que esa carpeta sí se versionee (para que las notas internas lleguen a todas las máquinas). La exclusión del build público la hace ignorePatterns de Quartz, no .gitignore. No reactivar la regla en .gitignore o se rompe la sincronización.


5. Cómo crear el token de Cloudflare (one-time + cuando expire)

El token NO se commitea nunca. Se guarda en GitHub Secrets con gh secret set y se destruye la copia local.

5.1 Crear el token en el dashboard

  1. Ir a https://dash.cloudflare.com/profile/api-tokens
  2. Botón “Create Token”.
  3. Bajar hasta el final de la lista de plantillas → junto a “Custom Token”“Get started”.
    • La plantilla “Edit Cloudflare Workers” no vale — la de Pages no existe como plantilla predefinida; por eso se usa Custom Token.
  4. Token name: blog-wiki-deploy (o algo descriptivo; sirve para identificarlo luego en la lista).
  5. Permissions:
    • Primer menú: Account
    • Segundo menú: Cloudflare Pages
    • Tercer menú: Edit
  6. Account Resources: IncludeSpecific account → la cuenta con ID 43c5d4a754f2951ba36185df5deef60c (o All accounts si solo hay una).
  7. (Opcional pero recomendado) Client IP Address Filtering: añadir la IP de salida habitual para reducir exposición.
  8. (Opcional) TTL: establecer fecha de expiración o dejar sin caducidad.
  9. Continue to summary → Create Token.
  10. Copiar el valor generado (formato cfut_... o similar). Solo aparece una vez. Guardarlo en un gestor de contraseñas.

5.2 Subir el token a los repos GitHub como secret

$env:CLOUDFLARE_API_TOKEN = "<token-pegado-aqui>"
 
gh secret set CLOUDFLARE_API_TOKEN --repo PoisonXploIT/blog --body $env:CLOUDFLARE_API_TOKEN
gh secret set CLOUDFLARE_ACCOUNT_ID --repo PoisonXploIT/blog --body "43c5d4a754f2951ba36185df5deef60c"
 
gh secret set CLOUDFLARE_API_TOKEN --repo PoisonXploIT/wiki  --body $env:CLOUDFLARE_API_TOKEN
gh secret set CLOUDFLARE_ACCOUNT_ID --repo PoisonXploIT/wiki  --body "43c5d4a754f2951ba36185df5deef60c"

5.3 Verificar que los secrets están

gh secret list --repo PoisonXploIT/blog   # debe mostrar CLOUDFLARE_API_TOKEN y CLOUDFLARE_ACCOUNT_ID
gh secret list --repo PoisonXploIT/wiki

5.4 Limpiar variables del entorno local

Remove-Item Env:\CLOUDFLARE_API_TOKEN
# (alternativa: cerrar la terminal)

5.5 Regenerar un token

Si el token caduca o se compromete (p. ej. quedó en un log de chat): ir a la lista de tokens en el dashboard → sobre el token blog-wiki-deployRoll (regenera un nuevo valor) o Delete y crear uno nuevo. Repetir 5.2.


6. Instalación/auth de la GitHub CLI (gh)

6.1 Login inicial

gh auth login
# elegir GitHub.com → HTTPS → autenticar en navegador

6.2 Scope workflow (necesario solo si vas a tocar .github/workflows/*.yml)

GitHub exige scope workflow para pushear commits que modifican archivos bajo .github/workflows/. Si un push falla con:

! [remote rejected] main -> main (refusing to allow an OAuth App to create or update workflow ... without 'workflow' scope)

Ejecuta (abre navegador para autorizar):

gh auth refresh -h github.com -s workflow

Verificar:

gh auth status
# Token scopes debe listar 'workflow' entre los demas

El 2026-07-06 lo hicimos y los scopes quedaron como: admin:org admin:org_hook admin:public_key admin:repo_hook delete_repo gist repo workflow write:packages.

6.3 Token clásico como alternativa

Si gh auth refresh no funciona (token OAuth no actualizable), crear un Classic PAT en https://github.com/settings/tokens con scopes repo + workflow, y luego:

$env:GH_TOKEN = "<token-classic>"
# o persistente:
gh auth login --with-token

⚠️ El token clásico también queda en logs si lo pegas en chat/terminal sin cuidado. Mejor usar gh auth refresh interactivo si se puede.


7. Workflows (referencia yaml completa)

7.1 Blog .github/workflows/deploy.yml

name: Deploy Blog to Cloudflare Pages
 
on:
  push:
    branches: [main]
  workflow_dispatch:
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
 
      - name: Install dependencies
        run: npm ci
 
      - name: Build Quartz
        run: npx quartz build
 
      - name: Deploy to Cloudflare Pages
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy public --project-name=blog

7.2 Wiki .github/workflows/deploy.yml

Idéntico salvo el --project-name:

name: Deploy Wiki to Cloudflare Pages
 
on:
  push:
    branches: [main]
  workflow_dispatch:
 
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
 
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
 
      - name: Install dependencies
        run: npm ci
 
      - name: Build Quartz
        run: npx quartz build
 
      - name: Deploy to Cloudflare Pages
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy public --project-name=sammideblas-wiki

7.3 Detalles de los pasos

StepQué hacePor qué
actions/checkout@v4 con fetch-depth: 0Clona el repo completo (toda la historia)Necesario para que Quartz pueda calcular fechas de modified de cada nota
actions/setup-node@v4 (Node 22) + cache: npmInstala Node con caché de npmCuellos de la versión 22 exigida en package.json (engines.node: ">=22")
npm ciInstala dependencias usando package-lock.json (reproducible)Mejor que npm install en CI
npx quartz buildGenera public/ con el sitio estáticoMismo comando que en local
cloudflare/wrangler-action@v3Ejecuta wrangler pages deploy public --project-name=...Sube public/ a CF Pages. Detecta automáticamente branch y commit hash para etiquetar el deployment

El workflow también dispara en workflow_dispatch (manual desde la web de Actions) — útil para re-deployar sin tener que hacer un commit vacío.

7.4 Workflows legacy/auxiliares (NO tocar salvo saber qué haces)

En .github/workflows/ de ambos repos existen otros archivos:

  • ci.yaml — checks de TypeScript + Prettier
  • build-preview.yaml + deploy-preview.yaml — previews para Pull Requests (legados de Quartz upstream, no usados activamente porque no hacemos PRs)
  • docker-build-push.yaml — solo en algunos
  • dependabot.yml — actualiza dependencias automáticamente

Los dos workflows deploy.yml son los únicos que importan para el deploy.


8. Verificar un deploy

8.1 Via GitHub Actions

# Últimos runs
gh run list --repo PoisonXploIT/blog --limit 3
gh run list --repo PoisonXploIT/wiki --limit 3
 
# Detalle de un run concreto (watch en vivo)
gh run watch <run-id> --repo PoisonXploIT/blog --exit-status
# ó
gh run view <run-id> --repo PoisonXploIT/blog --json status,conclusion,jobs

8.2 Via API de Cloudflare (necesita token en entorno)

$env:CLOUDFLARE_API_TOKEN = "<token>"
$acc = "43c5d4a754f2951ba36185df5deef60c"
 
# Último deployment del blog
Invoke-RestMethod `
  -Uri "https://api.cloudflare.com/client/v4/accounts/$acc/pages/projects/blog/deployments?per_page=1" `
  -Headers @{Authorization="Bearer $env:CLOUDFLARE_API_TOKEN"}
 
# Lo mismo para el wiki
Invoke-RestMethod `
  -Uri "https://api.cloudflare.com/client/v4/accounts/$acc/pages/projects/sammideblas-wiki/deployments?per_page=1" `
  -Headers @{Authorization="Bearer $env:CLOUDFLARE_API_TOKEN"}

8.3 Endpoints públicos

Tras un push, el deployment CF Pages tarda ~1 min en estar activo en el dominio custom. La URL https://<id>.blog-f5e.pages.dev (preview subdomain) está disponible inmediatamente بعد del job, mientras que el dominio custom puede tardar unos segundos más.

8.4 Conectar los dominios custom a un deployment

Ya configurado en el dashboard de Cloudflare Pages para cada proyecto:

  • blogsammideblas.com, blog.sammideblas.com, www.sammideblas.com (CNAMEs a blog-f5e.pages.dev).
  • sammideblas-wikidoc.sammideblas.com (CNAME a sammideblas-wiki.pages.dev).
  • Production branch main en ambos. Cada push a main crea un deployment production (no preview).

Si añadieras un dominio nuevo: dashboard de CF → Pages → el proyecto → “Custom domains” → “Set up a custom domain”. Cloudflare te guía automáticamente.


9. Deploy local manual (alternativa a Actions, sin GitHub)

Útil para pruebas o cuando Actions está caído. Necesitas el API token de CF en variables.

$env:CLOUDFLARE_API_TOKEN = "<token>"
$env:CLOUDFLARE_ACCOUNT_ID = "43c5d4a754f2951ba36185df5deef60c"
 
cd C:\Users\Sammi De Blas\hermes_data\blog-build  # o wiki-build
 
# Build
npx quartz build
# genera public\
 
# Deploy (el flag --commit-dirty=true evita el warning de working tree sucio)
npx wrangler@latest pages deploy public --project-name=blog --commit-dirty=true
# o
npx wrangler@latest pages deploy public --project-name=sammideblas-wiki --commit-dirty=true

⚠️ El deploy local no dispara Actions ni commit. Eso significa que el estado de Cloudflare y GitHub pueden quedar desincronizados. Úsalo solo en emergencias o pruebas; para producción, siempre git push.

Para hacer el deploy local pero dejar el repo en el mismo commit que el deployment: git add . && git commit -m "manual deploy" && git push, después el siguiente git push de alguien re-deployará con el mismo contenido.


10. Cómo añadir un subdominio nuevo (p. ej. sec.sammideblas.com)

Si en el futuro quieres publicar otro Quartz (u otro sitio) en un subdominio de sammideblas.com:

10.1 Crear proyecto en Cloudflare Pages

  1. Dashboard CF → Workers & PagesCreate applicationPagesUpload assets (o “Connect to Git” si es repo nuevo).
  2. Nomear el proyecto (p. ej. sammideblas-sec).
  3. NO hace falta build configuration si subes el public/ directamente desde CI.
  4. Apuntar el subdominio: proyecto → Custom domainsSet up a custom domainsec.sammideblas.com. Cloudflare añade el CNAME automáticamente.

10.2 Crear repo en GitHub

gh repo create PoisonXploIT/sec-source --private
# clonar, montar Quartz, commitear

10.3 Fijar secrets + workflow

gh secret set CLOUDFLARE_API_TOKEN --repo PoisonXploIT/sec-source --body $env:CLOUDFLARE_API_TOKEN
gh secret set CLOUDFLARE_ACCOUNT_ID --repo PoisonXploIT/sec-source --body "43c5d4a754f2951ba36185df5deef60c"

Copia el deploy.yml del blog cambiando --project-name=sammideblas-sec. Commit + push.

10.4 Documentar

  • Actualizar esta nota (secciones 0 y 3 con el nuevo repo/proyecto/ruta local).
  • Copiar la actualización a .github/DEPLOY.md y AGENTS.md de los repos hermanos.
  • Añadir el nuevo subdominio al ignorePatterns/baseUrl del Quartz correspondiente.

11. Estado de los CF Pages projects (tabla de referencia)

BlogWiki
Nombre proyectoblogsammideblas-wiki
Project IDbdba5fc0-9e9a-4d5f-b9bb-2d92ec75455118d6cbcc-89a6-44ba-985e-7a9fa128707d
pages.dev subdomainblog-f5e.pages.devsammideblas-wiki.pages.dev
Alias (custom domains)sammideblas.com, blog.sammideblas.com, www.sammideblas.comdoc.sammideblas.com
Production branchmainmain
Fecha creación project2026-05-092026-05-14
Framework framework"" (Quartz detecta HTML estático)""
compatibility_date2026-05-092026-05-14
Last deployment typead_hoc (originally) / wrangler-action (now)ad_hoc originally / wrangler-action now
Build command (CF config)null (build hecho en GH Actions)null
Build image major version33

⚠️ Algunos de estos campos (Build command, compatibility_date, build_image_major_version) son internos de Cloudflare y no se tocan nunca desde aquí. Solo aparecen como referencia.


12. Documentación para agentes (dónde vive cada copia)

UbicaciónVisible paraRol
C:\Users\Sammi De Blas\Desktop\OBSIDIAN VAULT\Destino\PROCEDIMIENTOS\PROCEDIMIENTO - Deploy Blog y Wiki a Cloudflare Pages.md (esta nota)Sammi (vault Obsidian principal del Escritorio)Documentación canónica que lee el operador
PoisonXploIT/wikicontent/PROCEDIMIENTOS/deploy-blog-wiki.mdAgentes que clonen wikiVersión en el repo publicable (filtrada por ignorePatterns, no se publica en doc.sammideblas.com pero SÍ en GitHub)
PoisonXploIT/blog.github/DEPLOY.mdAgentes que clonen blogCopia espejo del anterior
PoisonXploIT/blog y PoisonXploIT/wikiAGENTS.md (raíz)Agentes (lo buscan por convención)Puntero corto a los procedimientos + reglas

Sincronización manual: si cambias algo aquí (vault del Escritorio), debes propagar a los otros 3 sitios a mano. Pendiente de crear un script que lo automatice.


13. Troubleshooting exhaustivo

13.1 Push rechazado por workflow scope

Síntoma:

! [remote rejected] main -> main (refusing to allow an OAuth App to create or update workflow
`.github/workflows/deploy.yml` without `workflow` scope)
error: failed to push some refs to 'https://github.com/PoisonXploIT/<repo>.git'

Causa: el token OAuth de gh no tiene scope workflow (necesario para pushear commits que tocan .github/workflows/*.yml).

Fix:

gh auth refresh -h github.com -s workflow
gh auth status   # verificar 'workflow' en los scopes
# reintentar el push

13.2 Workflow success pero el sitio no se actualiza

Causa posible 1: workflow disparado desde una rama que no es main (no production branch). Comprobar el on.push.branches del deploy.yml.

Causa posible 2: --project-name erróneo en el workflow. Verificar que coincide exactamente con el nombre del CF Pages project (blog o sammideblas-wiki).

Causa posible 3: el dominio custom está apuntando a otro deployment. Ver dashboard de CF Pages → proyecto → “Deployments” → mirar cuál es el “Production” más reciente.

13.3 npm ci falla en Actions con lockfile out of sync

Causa: package-lock.json no se commiteó al cambiar dependencias.

Fix local:

cd <blog-build|wiki-build>
npm install   # actualiza package-lock.json
git add package.json package-lock.json
git commit -m "chore(deps): <descripcion>"
git push origin main

13.4 Workflow falla en Build Quartz con error TypeScript

Probablemente un typo en quartz.config.ts o quartz.layout.ts. Ver logs del step: gh run view <id> --repo <repo> --log-failed.

En local, reproducir con npx quartz build y arreglar. También sirve para validar antes de pushear:

npx tsc --noEmit       # typecheck
npx prettier . --check # formato
# si pasa, deberia desplegar bien

13.5 Deploy Cloudflare falla con Authentication error

Causa: secret CLOUDFLARE_API_TOKEN caducado o con permisos insuficientes.

Fix:

  1. Regenerar token (sección 5.1).
  2. gh secret set CLOUDFLARE_API_TOKEN --repo <repo> --body "<nuevo-token>".
  3. Re-ejecutar el workflow desde la web: Actions → run → “Re-run failed jobs” o gh run rerun <id> --repo <repo> --failed.

13.6 Push falla con Repository not found

Causa posible 1: el remote está apuntando a un repo que no existe (sucedió con el blog antes del 2026-07-06, cuando PoisonXploIT/blog no estaba creado).

Fix:

gh repo create PoisonXploIT/blog --private
git remote set-url origin https://github.com/PoisonXploIT/blog.git
git push -u origin main

Causa posible 2: no tienes permisos en el repo. gh auth status y verificar el user.

13.7 Imágenes externas no cargan en el sitio publicado

Causa probable: CSP (Content Security Policy) restrictiva en quartz.config.ts que bloquea el dominio de la imagen.

Las imágenes en https://blogger.googleusercontent.com/... ya están permitidas por commits previos (13c0d53, afbe80f). Si añades imágenes desde un dominio nuevo, hay que:

  1. Editar quartz.config.ts → sección CSP → añadir el dominio.
  2. Valida los permisos de la rama de imágenes (p. ej. img-src).

13.8 Nota nueva en content/PROCEDIMIENTOS/ no aparece en doc.sammideblas.com

Esperado. La carpeta content/PROCEDIMIENTOS/ está en ignorePatterns de quartz.config.ts → no se compila → no se publica. Pero SÍ se commitea al repo y se sincroniza con todas las máquinas vía git. That’s by design.

13.9 La página de un post devuelve 404

Podría ser:

  • El draft: true en frontmatter (filtrado por Quartz).
  • Slug mal calculado (caracteres raros, espacios). El archivo debe llamarse 0xNN-slug-con-guiones.md (sin espacios, sin acentos).
  • El post no está commiteado o el deploy aún está corriendo.

Verificar:

gh run list --repo PoisonXploIT/blog --limit 1
# esperarse al "completed / success" antes de checkear el URL
# (la URL aparece en el último run: gh run view <id> ... --log)

13.10 El wiki se publica con notas internas que no deberían salir

Causa: añadiste una carpeta sensible y no la metiste en ignorePatterns. Editar quartz.config.ts:

ignorePatterns: [..., "NUEVA_CARPETA", "NUEVA_CARPETA/*"],

Hacer commit + push. El próximo deploy la filtra.

13.11 Cambié algo en quartz.config.ts y se rompe el build

Quartz es sensible a la sintaxis TypeScript/JSX. Verificar siempre en local antes de pushear:

npx tsc --noEmit     # typecheck
npx quartz build     # dry-run build

13.12 Cloudflare falla con wrangler-action@v3 “could not authenticate”

El secret CLOUDFLARE_ACCOUNT_ID no está fijado o su valor tiene caracteres extra. Verificar:

gh secret list --repo <repo>

Si falta, re-crearlo (sección 5.2). El valor 43c5d4a754f2951ba36185df5deef60c debe ir sin comillas ni espacios.

13.13 Build de Quartz muy lento en CI (>10 min)

Probablemente muchas notas o notas muy grandes en el wiki. Cosas a probar:

  • npx quartz build -d docs para ver logs verbosos.
  • Limitar --concurrency según los recursos.
  • No suele ser un problema; la wiki tarda ~3-8 min con ~250 notas.

13.14 Privacy: token pegado accidentalmente en chat/log

Acción inmediata:

  1. Dashboard Cloudflare → lista de tokens → sobre el token comprometido → Roll (regenera valor) o Delete.
  2. Si lo deleteas, crear uno nuevo (sección 5.1).
  3. gh secret set CLOUDFLARE_API_TOKEN --repo PoisonXploIT/blog --body "<nuevo>"
  4. gh secret set CLOUDFLARE_API_TOKEN --repo PoisonXploIT/wiki --body "<nuevo>"
  5. Re-ejecutar workflows para confirmar.

Regla de oro: nunca pegues el token en chat, commits, ni archivos del repo/vault. Los secretos viven en GitHub Secrets, no en texto plano en ninguna parte.


14. Historia (contexto para agentes futuros)

Antes del 2026-07-06:

  • El blog no tenía repo GitHub (PoisonXploIT/blog devolvía 404). Solo existía localmente en la máquina principal.
  • El deploy del blog era manual con npx wrangler pages deploy public --project-name=blog desde un host Linux en /opt/data/blog-build. El token OAuth de wrangler vivía en ~/.config/.wrangler de ese Linux (NO se sincronizaba con el mirror Windows hermes_data).
  • El workflow del wiki (deploy.yml) estaba numéricamente apuntando a GitHub Pages (con actions/deploy-pages@v4) en vez de Cloudflare Pages. GitHub Pages no estaba activado en el repo (gh api .../pages devolvía 404). El deploy real del wiki también era manual con wrangler.
  • Ningún AGENTS.md existía.
  • Una nueva máquina nunca podía encontrar ni la fuente ni el flujo de deploy sin preguntar.

Cambio del 2026-07-06 (motivado por el deploy del post 0x70):

  1. Tras descubrir (vía logs en ~/.config/.wrangler/logs/) que el deploy real era wrangler-mánual y que Cloudflare Account ID era 43c5d4a754f2951ba36185df5deef60c, se ejecutó un deploy manual del post 0x70 (commit 9aa5aee) desde Windows → deployment 508eb468.blog-f5e.pages.dev live en sammideblas.com.
  2. Se detectó que el remote PoisonXploIT/blog era 404 → gh repo create PoisonXploIT/blog --private.
  3. Se unificó el flujo: GitHub Actions deploy.yml con cloudflare/wrangler-action@v3 en ambos repos, secrets CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID en cada repo.
  4. Para el wiki, el deploy.yml se reescribió de GitHub Pages → Cloudflare Pages.
  5. Se amplió el scope OAuth de gh con workflow (gh auth refresh -h github.com -s workflow) para poder pushear los commits que reescribían deploy.yml.
  6. Push inicial del blog: 67 commits acumulados + post 0x70 + workflow nuevo → workflow success (run 28812468087, 57s).
  7. Push del wiki: commit de93600 con workflow nuevo → workflow success (run 28812453786).
  8. Se creó la documentación:
    • PoisonXploIT/wikicontent/PROCEDIMIENTOS/deploy-blog-wiki.md (versionada, filtrada del build público por ignorePatterns).
    • Se quitó la regla content/PROCEDIMIENTO* del .gitignore del wiki para que esa carpeta sí se versionee.
    • PoisonXploIT/blog.github/DEPLOY.md (copia espejo).
    • AGENTS.md en ambos repos (puntero corto, reglas críticas).
    • Esta nota en el vault Obsidian principal del Escritorio (copia canónica para el operador).

A partir de esa fecha:

  • Deploy manual con wrangler ya NO es necesario para producción.
  • Cualquier máquina puede desplegar con git clone + git push.
  • Cualquier agente IA encuentra el procedimiento leyendo AGENTS.md + DEPLOY.md (blog) o content/PROCEDIMIENTOS/deploy-blog-wiki.md (wiki), sin preguntar.

15. Comandos rápidos (cheatsheet)

# === BLOG ===
cd C:\Users\Sammi De Blas\hermes_data\blog-build
npx quartz build --serve               # preview local
git add content\posts\0xNN-slug.md
git commit -m "post: 0xNN Título"
git push origin main                   # despliega solo
gh run list --repo PoisonXploIT/blog --limit 1   # estado del deploy
 
# === WIKI ===
cd C:\Users\Sammi De Blas\hermes_data\wiki-build
git add content\<ruta>
git commit -m "wiki: descripción"
git push origin main                   # despliega solo
gh run list --repo PoisonXploIT/wiki --limit 1
 
# === Secrets (solo si caduca el token) ===
$env:CLOUDFLARE_API_TOKEN = "<token>"
gh secret set CLOUDFLARE_API_TOKEN --repo PoisonXploIT/blog --body $env:CLOUDFLARE_API_TOKEN
gh secret set CLOUDFLARE_ACCOUNT_ID --repo PoisonXploIT/blog --body "43c5d4a754f2951ba36185df5deef60c"
gh secret set CLOUDFLARE_API_TOKEN --repo PoisonXploIT/wiki --body $env:CLOUDFLARE_API_TOKEN
gh secret set CLOUDFLARE_ACCOUNT_ID --repo PoisonXploIT/wiki --body "43c5d4a754f2951ba36185df5deef60c"
Remove-Item Env:\CLOUDFLARE_API_TOKEN
 
# === Deploy local manual (alternativa, NO usar de rutina) ===
$env:CLOUDFLARE_API_TOKEN = "<token>"
$env:CLOUDFLARE_ACCOUNT_ID = "43c5d4a754f2951ba36185df5deef60c"
npx quartz build
npx wrangler@latest pages deploy public --project-name=blog --commit-dirty=true             # blog
npx wrangler@latest pages deploy public --project-name=sammideblas-wiki --commit-dirty=true # wiki
 
# === Verificar deploys CF (opcional) ===
$acc = "43c5d4a754f2951ba36185df5deef60c"
Invoke-RestMethod "https://api.cloudflare.com/client/v4/accounts/$acc/pages/projects/blog/deployments?per_page=1" -Headers @{Authorization="Bearer $env:CLOUDFLARE_API_TOKEN"}
 
# === Auth gh (si caduca o falta scope workflow) ===
gh auth login
gh auth refresh -h github.com -s workflow
gh auth status

16. Reglas de oro para agentes IA

  1. Antes de tocar workflows o publicar: lee AGENTS.md + .github/DEPLOY.md del repo en cuestión (o esta nota del vault del Escritorio, que es la versión canónica).
  2. Tokens nunca en texto plano en commits, notas, chat, ni logs. Viven en GitHub Secrets (gh secret set). Si los ves commiteados, bórralos inmediatamente y regenera.
  3. Para desplegar, siempre que sea posible git push origin main. El deploy manual con wrangler es solo backup.
  4. El commit a main despliega solo — no ejecutar wrangler local sin razón (puede desincronizar CF y GitHub).
  5. La carpeta content/PROCEDIMIENTOS/ del wiki se versiona pero NO se publica (filtrada por ignorePatterns de quartz.config.ts). No reactivar la regla content/PROCEDIMIENTO* en .gitignore.
  6. Numeración de posts: hex consecutivo (0x71, 0x72…). Slug en inglés con guiones (0x71-titulo-del-post.md).
  7. Plantilla frontmatter siempre completa (title, date, draft: false, description, tags).
  8. Si un push falla por workflow scope: gh auth refresh -h github.com -s workflow (es interactivo, abre navegador).
  9. Si vas a añadir imagen desde dominio nuevo: revisar CSP en quartz.config.ts (13c0d53 y afbe80f son los commits que permitieron Blogger).
  10. Verificar siempre el estado del workflow tras un push: gh run list --repo PoisonXploIT/<repo> --limit 1. No asumir “Success” sin comprobar.

17. Apéndice: cómo reproducir toda la configuración desde cero

Si algún día hay que reconstruir la infraestructura (nueva cuenta CF, nuevo GitHub org, etc.) aquí están los pasos en orden.

17.1 Cloudflare

  1. Crear cuenta en https://dash.cloudflare.com.
  2. Workers & Pages → Create application → Pages → Upload assets → nomear proyecto blog. Repetir con sammideblas-wiki.
  3. Custom domains en cada proyecto: configurar CNAMEs a <project>.pages.dev desde el dashboard de DNS de Cloudflare para sammideblas.com, blog.sammideblas.com, www.sammideblas.com (blog) y doc.sammideblas.com (wiki).
  4. Profile → API Tokens → Create Token → Custom Token con Account → Cloudflare Pages → Edit para esa cuenta. Copiar el valor.

17.2 GitHub

  1. Crear org PoisonXploIT (o usuario).
  2. gh repo create PoisonXploIT/blog --private
  3. gh repo create PoisonXploIT/wiki --private
  4. Fijar secrets en cada repo (ver sección 5.2).

17.3 Repositorios locales

  1. git clone https://github.com/PoisonXploIT/blog.git <ruta>/blog-build
  2. git clone https://github.com/PoisonXploIT/wiki.git <ruta>/wiki-build
  3. En cada uno: copiar/econtrar el contenido Quartz (package.json, quartz.config.ts, quartz.layout.ts, content/, quartz/, static/…). Configurar baseUrl en quartz.config.ts (sammideblas.com o doc.sammideblas.com).
  4. npm install.

17.4 Workflows

Crear .github/workflows/deploy.yml en cada repo (copiar de sección 7).

17.5 Documentación

  • AGENTS.md en cada repo (raíz).
  • .github/DEPLOY.md en el blog (copia de la nota del wiki).
  • content/PROCEDIMIENTOS/deploy-blog-wiki.md en el wiki.
  • Esta nota en el vault Obsidian del Escritorio.

17.6 Verificación final

# trigger manual del workflow desde la web de Actions ó:
gh workflow run deploy.yml --repo PoisonXploIT/blog
gh workflow run deploy.yml --repo PoisonXploIT/wiki
# esperar y visitar URLs publicas

18. Metadatos de mantenimiento

  • Última actualización canónica: 2026-07-06 (Pull del post 0x70)
  • Autor: Sammi De Blas (con asistencia de agente IA glm-5.2 vía opencode)
  • Ubicaciones espejo:
    • PoisonXploIT/wikicontent/PROCEDIMIENTOS/deploy-blog-wiki.md
    • PoisonXploIT/blog.github/DEPLOY.md
    • PoisonXploIT/wiki y PoisonXploIT/blogAGENTS.md
  • Verificación de hashes (_COMMITS):
    • Blog workflow unificado: 6747ae1
    • Wiki workflow unificado: de93600
    • Wiki nota procedimiento: 0c47726
    • Blog AGENTS+DEPLOY: 5a7e71d
    • Wiki AGENTS: 2e70a6e
    • Último deploy blog en production: 9aa5aee (post 0x70) → deployment 508eb468.blog-f5e.pages.dev
    • Último deploy wiki en production: 95d3cf3e (antes del cambio de workflow)

EOF

“La verdad está ahí fuera. Y el aire está cargado de información.”