Cloudflare Pages Deployment

This site is deployed to Cloudflare Pages with Zero Trust authentication.

Architecture

Component Value

URL

docs.domusdigitalis.dev

Platform

Cloudflare Pages

Build Tool

Antora

Authentication

Cloudflare Access (Zero Trust)

Build Configuration

Cloudflare Pages Settings

Setting Value

Build command

./build.sh

Output directory

build/site

Node version

22 (auto-detected)

Environment Variables

Variable Description Location

CF_ANTORA_GIT_TOKEN

GitHub PAT for cloning private repos

Cloudflare Pages → Settings → Environment Variables

Token Requirements

Fine-grained GitHub PAT with:

  • Repository access: All 15 domus-* repos

  • Permissions: Contents (read-only)

Repos requiring access:

  • domus-docs

  • domus-infra-ops

  • domus-ise-linux

  • domus-ise-windows

  • domus-ise-ops

  • domus-netapi-docs

  • domus-secrets-ops

  • domus-linux-ops

  • domus-python

  • domus-identity-ops

  • domus-captures

  • domus-automation-ops

  • domus-siem-ops

  • domus-o11y-ops

  • domus-windows-ops

Build Script

The build.sh script orchestrates the full build pipeline:

  1. Validates required environment variables (CF_ANTORA_GIT_TOKEN, CF_ACCESS_CLIENT_ID, CF_ACCESS_CLIENT_SECRET)

  2. Downloads the UI bundle from ui.domusdigitalis.dev with Cloudflare Access authentication

  3. Validates the bundle (minimum 50 KB, ZIP integrity, theme CSS presence)

  4. Creates a temporary playbook with GitHub token injected into URLs

  5. Runs Antora with --quiet to prevent token exposure in logs

  6. Cleans up the temporary playbook on exit (via trap)

#!/bin/bash
set -euo pipefail

# Required environment variables
: "${CF_ANTORA_GIT_TOKEN:?Missing GitHub PAT}"
: "${CF_ACCESS_CLIENT_ID:?Missing Cloudflare Access Client ID}"
: "${CF_ACCESS_CLIENT_SECRET:?Missing Cloudflare Access Client Secret}"

# Download UI bundle with Cloudflare Access auth + retry logic
curl -fsSL -o ui-bundle.zip \
  -H "CF-Access-Client-Id: ${CF_ACCESS_CLIENT_ID}" \
  -H "CF-Access-Client-Secret: ${CF_ACCESS_CLIENT_SECRET}" \
  "https://ui.domusdigitalis.dev/ui-bundle.zip?v=$(date +%s)"

# Create temp playbook with token injection (never modify original)
PLAYBOOK_TMP=$(mktemp)
trap "rm -f $PLAYBOOK_TMP" EXIT
sed "s|https://github.com/EvanusModestus/|https://${CF_ANTORA_GIT_TOKEN}@github.com/EvanusModestus/|g" \
  antora-playbook.yml > "$PLAYBOOK_TMP"

# Build (--quiet prevents token in logs)
npx antora --quiet "$PLAYBOOK_TMP"
The --quiet flag and temporary playbook pattern prevent token exposure in build logs.

Cloudflare Access

The site is protected by Cloudflare Zero Trust Access.

Configuration

Setting Value

Application name

Domus Docs

Domain

docs.domusdigitalis.dev

Authentication

Email-based

Access Applications

Two Access applications are required for proper operation:

Application Domain Purpose

domus-docs

docs.domusdigitalis.dev

Protects documentation content (requires authentication)

domus-docs-static

docs.domusdigitalis.dev/_/*

Bypasses authentication for static assets (CSS, JS, fonts)

Without the static assets bypass, the site will appear white/unstyled because CSS files return 302 redirects to the Access login page.

Managing Access via netapi

# List all Access applications
netapi cloudflare access apps

# List policies for an application
netapi cloudflare access policies <app-id>

# Create static assets bypass (one command)
netapi cloudflare access bypass-static docs.domusdigitalis.dev

See netapi cloudflare commands for full documentation.

Managing Access via API

# List Access applications
curl -s "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" | jq '.result[] | {id, name, domain}'

# Create static bypass app
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"name": "domus-docs-static", "type": "self_hosted", "domain": "docs.domusdigitalis.dev/_/*"}'

# Add bypass policy
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/access/apps/<APP_ID>/policies" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"name": "Bypass Static Assets", "decision": "bypass", "include": [{"everyone": {}}]}'

Login Page Branding

Setting Value

Background color

#1a1a1a

Organization name

Domus Digitalis

Header text

(custom message)

Customized at: Zero Trust → Reusable components → Custom pages → Access login page

Deployment Workflow

  1. Make changes to any domus-* repo

  2. Push to GitHub

  3. Cloudflare Pages automatically rebuilds (via deploy hooks)

All content repos have webhooks configured to trigger rebuilds on push.

Deploy Hooks

Deploy hooks enable automatic rebuilds when any content repo is updated.

Architecture

domus-infra-ops     ──┐
domus-ise-linux     ──┤
domus-ise-windows   ──┤
domus-ise-ops       ──┤
domus-netapi-docs   ──┤
domus-secrets-ops   ──┼──→ GitHub Webhook ──→ Cloudflare Deploy Hook ──→ Rebuild
domus-linux-ops     ──┤
domus-python        ──┤
domus-identity-ops  ──┤
domus-captures      ──┤
domus-automation-ops──┤
domus-siem-ops      ──┤
domus-o11y-ops      ──┤
domus-windows-ops   ──┘

Configured Webhooks

Repository Webhook Status

domus-infra-ops

✓ Active

domus-ise-linux

✓ Active

domus-ise-windows

✓ Active

domus-ise-ops

✓ Active

domus-netapi-docs

✓ Active

domus-secrets-ops

✓ Active

domus-linux-ops

✓ Active

domus-python

✓ Active

domus-identity-ops

✓ Active

domus-captures

✓ Active

domus-automation-ops

✓ Active

domus-siem-ops

✓ Active

domus-o11y-ops

✓ Active

domus-windows-ops

✓ Active

Setup (For Reference)

1. Create Deploy Hook in Cloudflare

  1. Cloudflare Dashboard → Pages → domus-docs → Settings → Builds & deployments

  2. Scroll to Deploy hooks

  3. Click Add deploy hook

  4. Name: github-content-repos

  5. Copy the generated URL

2. Add Webhook to GitHub Repos

HOOK_URL="https://api.cloudflare.com/client/v4/pages/webhooks/deploy_hooks/YOUR_HOOK_ID"

for repo in domus-infra-ops domus-ise-linux domus-ise-windows domus-ise-ops domus-netapi-docs domus-secrets-ops domus-linux-ops domus-python domus-identity-ops domus-captures domus-automation-ops domus-siem-ops domus-o11y-ops domus-windows-ops; do
  gh api repos/EvanusModestus/$repo/hooks \
    --method POST \
    -f name=web \
    -f "config[url]=$HOOK_URL" \
    -f "config[content_type]=json" \
    -F "events[]=push" \
    -F active=true
done

3. Verify Webhooks

# List webhooks on a repo
gh api repos/EvanusModestus/domus-infra-ops/hooks --jq '.[] | "ID: \(.id) | Events: \(.events)"'

# Check recent deliveries in GitHub
# Repo → Settings → Webhooks → (click hook) → Recent Deliveries

Troubleshooting

Webhook Not Triggering

  1. Check GitHub → Repo → Settings → Webhooks → Recent Deliveries

  2. Look for failed deliveries (red X)

  3. Verify Cloudflare deploy hook URL is still valid

Manual Rebuild

If webhooks fail or you need to force a rebuild after pushing to spoke repos:

# One-liner: Empty commit + push to all remotes
cd /home/evanusmodestus/atelier/_bibliotheca/domus-docs && \
  git commit --allow-empty -m "chore: trigger rebuild" && \
  git remote | xargs -I {} git push {} main
Example output
[main 13b190b] chore: trigger rebuild
To gitea-01.inside.domusdigitalis.dev:evanusmodestus/domus-docs.git
   1daa4d7..13b190b  main -> main
To gitlab.com:EvanusModestus/domus-docs.git
   1daa4d7..13b190b  main -> main
To github.com:EvanusModestus/domus-docs.git
   6f5e816..13b190b  main -> main

Alternative methods:

# Option 2: Push to GitHub only (triggers Cloudflare)
git push origin main

# Option 3: Cloudflare Dashboard
# Pages → domus-docs → Deployments → Retry deployment

# Option 4: Cloudflare API (see CI Trigger Endpoint below)

CI Trigger Endpoint (Roadmap)

Not yet implemented. This section outlines the planned automation.

Goal

Create a dedicated endpoint or script that can be called to trigger rebuilds programmatically, useful for:

  • Post-merge hooks from spoke repos

  • Scheduled rebuilds (e.g., daily to catch dependency updates)

  • Integration with CI/CD pipelines

  • Slack/Discord bot commands

Planned Implementation

Option A: Direct Cloudflare API

# Trigger rebuild via Cloudflare Pages API
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${CF_ACCOUNT_ID}/pages/projects/domus-docs/deployments" \
  -H "Authorization: Bearer ${CF_API_TOKEN}" \
  -H "Content-Type: application/json"

Option B: netapi Integration

# Planned: netapi cloudflare pages rebuild domus-docs
netapi cloudflare pages rebuild domus-docs

Option C: GitHub Actions Workflow Dispatch

# .github/workflows/rebuild.yml
name: Trigger Rebuild
on:
  workflow_dispatch:
  repository_dispatch:
    types: [rebuild]

jobs:
  trigger:
    runs-on: ubuntu-latest
    steps:
      - run: |
          curl -X POST "${{ secrets.CF_DEPLOY_HOOK_URL }}"

Then trigger from any repo:

gh workflow run rebuild.yml --repo EvanusModestus/domus-docs

Status

[ ] Create Cloudflare API token with Pages deploy permission [ ] Add netapi cloudflare pages subcommands [ ] Create GitHub Actions workflow dispatch [ ] Document trigger endpoint in dsec secrets

Troubleshooting

Token Issues

# Test token locally
dsource d000 dev/app
curl -s -H "Authorization: token $CF_ANTORA_GIT_TOKEN" \
  https://api.github.com/repos/EvanusModestus/domus-infra-ops | head -5

Expected: JSON with repo info. If 401/404, token is invalid or missing permissions.

Content repos must use relative symlinks, not absolute paths:

# Bad (breaks in CI)
/home/user/path/to/file.adoc

# Good (works everywhere)
../../../../../../runbooks/file.adoc

Secrets Management

Token stored in dsec:

dsec edit d000 dev/app

Under # --- Cloudflare Pages --- section.