Skip to content

Documentation Site Implementation Plan

For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

Goal: Deploy a searchable MkDocs Material documentation site on Cloudflare Pages with Access protection.

Architecture: MkDocs builds markdown from docs/ into a static site. GitHub Actions deploys to Cloudflare Pages on push. Cloudflare Access protects with Authentik/GitHub/OTP authentication, restricted to approved email domains.

Tech Stack: MkDocs Material, Cloudflare Pages, Cloudflare Access, Cloudflare Tunnel, Terraform, GitHub Actions

Design Document: docs/plans/2025-12-29-docs-site-design.md


Phase 1: Foundation

Task 1: Create MkDocs Configuration

Files: - Create: mkdocs.yml

Step 1: Create mkdocs.yml

site_name: fzymgc-house Cluster Docs
site_url: https://docs.fzymgc.house
repo_url: https://github.com/fzymgc-house/selfhosted-cluster
repo_name: fzymgc-house/selfhosted-cluster

theme:
  name: material
  features:
    - navigation.instant
    - navigation.tracking
    - navigation.sections
    - navigation.expand
    - search.suggest
    - search.highlight
    - content.code.copy
  palette:
    - scheme: slate
      primary: deep purple
      accent: amber
      toggle:
        icon: material/brightness-4
        name: Switch to light mode
    - scheme: default
      primary: deep purple
      accent: amber
      toggle:
        icon: material/brightness-7
        name: Switch to dark mode

plugins:
  - search
  - tags

markdown_extensions:
  - admonition
  - pymdownx.details
  - pymdownx.superfences
  - pymdownx.tabbed:
      alternate_style: true
  - tables
  - toc:
      permalink: true

nav:
  - Home: index.md
  - Getting Started:
    - getting-started/index.md
    - Environment Setup: getting-started/environment.md
    - Cluster Access: getting-started/cluster-access.md
    - AI Tooling: getting-started/ai-tooling.md
  - Operations:
    - operations/index.md
    - Vault: operations/vault.md
    - Authentik: operations/authentik.md
    - Cloudflare: operations/cloudflare.md
    - HCP Terraform: operations/hcp-terraform.md
    - Windmill: operations/windmill.md
    - GitHub Tokens: operations/github-tokens.md
  - Reference:
    - reference/index.md
    - Services Catalog: reference/services.md
    - Technologies: reference/technologies.md
    - Network: reference/network.md
    - Secrets & Vault Paths: reference/secrets.md
  - Architecture:
    - architecture/index.md
    - System Overview: architecture/overview.md
    - Decisions: architecture/decisions/
  - Design Plans:
    - plans/index.md
    - Archive: plans/archive/

Step 2: Verify YAML is valid

Run: python -c "import yaml; yaml.safe_load(open('mkdocs.yml'))" Expected: No output (success)

Step 3: Commit

git add mkdocs.yml
git commit -m "feat(docs): add MkDocs Material configuration"

Task 2: Create Directory Structure

Files: - Create: docs/index.md - Create: docs/getting-started/index.md - Create: docs/operations/index.md - Create: docs/reference/index.md - Create: docs/architecture/index.md - Create: docs/architecture/decisions/.gitkeep - Modify: docs/plans/index.md (create if missing)

Step 1: Create docs/index.md

# fzymgc-house Cluster Documentation

Welcome to the documentation for the fzymgc-house self-hosted Kubernetes cluster.

## Quick Links

| Section | Description |
|---------|-------------|
| [Getting Started](getting-started/index.md) | Environment setup, cluster access, AI tooling |
| [Operations](operations/index.md) | How-to guides for Vault, Authentik, Cloudflare, etc. |
| [Reference](reference/index.md) | Services catalog, technologies, network info |
| [Architecture](architecture/index.md) | System design and decision records |
| [Design Plans](plans/index.md) | Current and archived design documents |

## About This Cluster

Self-hosted Kubernetes cluster on TuringPi 2 hardware (RK1 compute modules). Three-layer architecture:

| Layer | Location | Purpose |
|-------|----------|---------|
| Ansible | `ansible/` | Cluster deployment, node configuration, k3s installation |
| Terraform | `tf/` | Infrastructure configuration (Vault, Authentik, Grafana) |
| Kubernetes | `argocd/` | Application manifests, managed by ArgoCD |

**Stack:** k3s + Calico CNI, Vault (secrets), Authentik (SSO), Grafana + VictoriaMetrics (observability)

Step 2: Create docs/getting-started/index.md

# Getting Started

New to this cluster? Start here.

## Sections

| Guide | Description |
|-------|-------------|
| [Environment Setup](environment.md) | Python venv, Vault login, tool prerequisites |
| [Cluster Access](cluster-access.md) | OIDC authentication, kubeconfig setup |
| [AI Tooling](ai-tooling.md) | MCP servers for Claude Code integration |

## Prerequisites

- Access to Vault at `https://vault.fzymgc.house`
- Kubernetes context configured
- Python 3.11+ for Ansible work

Step 3: Create docs/operations/index.md

# Operations

How-to guides and procedures for cluster operations.

## Guides

| Service | Description |
|---------|-------------|
| [Vault](vault.md) | Secrets management, policies, Kubernetes auth |
| [Authentik](authentik.md) | SSO configuration, OIDC troubleshooting |
| [Cloudflare](cloudflare.md) | DNS, Tunnels, Workers |
| [HCP Terraform](hcp-terraform.md) | Remote execution, agents, GitOps |
| [Windmill](windmill.md) | Workflow automation |
| [GitHub Tokens](github-tokens.md) | PAT setup for Actions Runner Controller |

Step 4: Create docs/reference/index.md

# Reference

Current state documentation and catalogs.

## Catalogs

| Reference | Description |
|-----------|-------------|
| [Services Catalog](services.md) | All services, hostnames, auth methods |
| [Technologies](technologies.md) | Tech stack with documentation links |
| [Network](network.md) | IPs, CIDRs, DNS, VIPs |
| [Secrets & Vault Paths](secrets.md) | Secret locations and policies |

Step 5: Create docs/architecture/index.md

# Architecture

System design and decision records.

## Overview

- [System Overview](overview.md) — High-level architecture

## Decisions

Architecture Decision Records (ADRs) are stored in the [decisions/](decisions/) directory.

Step 6: Create docs/architecture/decisions/.gitkeep

mkdir -p docs/architecture/decisions
touch docs/architecture/decisions/.gitkeep

Step 7: Create docs/plans/index.md

# Design Plans

Design documents for features and changes.

## Active Plans

| Plan | Date | Status |
|------|------|--------|
| [Documentation Site](2025-12-29-docs-site-design.md) | 2025-12-29 | In Progress |

## Archive

Completed plans are moved to [archive/](archive/).

Step 8: Verify structure

Run: find docs -name "*.md" -o -name ".gitkeep" | sort Expected: All new files listed

Step 9: Commit

git add docs/
git commit -m "feat(docs): create MkDocs directory structure"

Task 3: Install MkDocs and Test Locally

Step 1: Install mkdocs-material

Run: pip install mkdocs-material Expected: Successfully installed mkdocs-material and dependencies

Step 2: Build docs (will have warnings for missing files)

Run: mkdocs build Expected: Builds with warnings about missing linked files (this is expected)

Step 3: Serve locally

Run: mkdocs serve Expected: Serving on http://127.0.0.1:8000/

Step 4: Verify in browser

Open: http://127.0.0.1:8000/ Expected: See homepage with navigation, search box, dark mode toggle

Step 5: Stop server (Ctrl+C)


Phase 2: Content Migration

Task 4: Move Existing Operations Docs

Files: - Move: docs/vault.mddocs/operations/vault.md - Move: docs/authentik.mddocs/operations/authentik.md - Move: docs/cloudflare.mddocs/operations/cloudflare.md - Move: docs/hcp-terraform.mddocs/operations/hcp-terraform.md - Move: docs/windmill.mddocs/operations/windmill.md - Move: docs/github-token-setup.mddocs/operations/github-tokens.md

Step 1: Move files

mv docs/vault.md docs/operations/vault.md
mv docs/authentik.md docs/operations/authentik.md
mv docs/cloudflare.md docs/operations/cloudflare.md
mv docs/hcp-terraform.md docs/operations/hcp-terraform.md
mv docs/windmill.md docs/operations/windmill.md
mv docs/github-token-setup.md docs/operations/github-tokens.md

Step 2: Verify moves

Run: ls docs/operations/ Expected: All 6 .md files plus index.md

Step 3: Test build

Run: mkdocs build --strict Expected: Build succeeds (may have some warnings)

Step 4: Commit

git add docs/
git commit -m "refactor(docs): move operations docs to new structure"

Task 5: Move Getting Started Docs

Files: - Move: docs/kubernetes-access.mddocs/getting-started/cluster-access.md - Move: docs/mcp-servers.mddocs/getting-started/ai-tooling.md - Create: docs/getting-started/environment.md

Step 1: Move files

mv docs/kubernetes-access.md docs/getting-started/cluster-access.md
mv docs/mcp-servers.md docs/getting-started/ai-tooling.md

Step 2: Create environment.md

# Environment Setup

Prerequisites and setup for working with this cluster.

## Python Environment

```bash
# One-time setup
./setup-venv.sh

# Before Ansible work
source .venv/bin/activate

Vault Authentication

export VAULT_ADDR=https://vault.fzymgc.house
vault login

# Verify connectivity
./scripts/vault-helper.sh status

Required Tools

Tool Purpose Install
kubectl Kubernetes CLI kubernetes.io
vault Vault CLI vaultproject.io
terraform IaC terraform.io
ansible Configuration management pip install ansible
gh GitHub CLI cli.github.com
**Step 3: Verify structure**

Run: `ls docs/getting-started/`
Expected: index.md, cluster-access.md, ai-tooling.md, environment.md

**Step 4: Commit**

```bash
git add docs/
git commit -m "refactor(docs): move getting-started docs to new structure"

Task 6: Create Reference Docs

Files: - Create: docs/reference/services.md - Create: docs/reference/technologies.md - Create: docs/reference/network.md - Create: docs/reference/secrets.md

Step 1: Create services.md (from Notion Services Catalog)

# Services Catalog

All services deployed in the cluster.

## Platform Services

| Service | Hostname | Namespace | Ingress Type | Auth Method |
|---------|----------|-----------|--------------|-------------|
| Vault | vault.fzymgc.house | vault | Traefik IngressRoute | Certificate |
| Authentik | auth.fzymgc.house | authentik | Traefik IngressRoute | Native |
| Grafana | grafana.fzymgc.house | monitoring | Traefik IngressRoute | OIDC |
| ArgoCD | argocd.fzymgc.house | argocd | Traefik IngressRoute | OIDC |

## Infrastructure Services

| Service | Hostname | Namespace | Ingress Type | Auth Method |
|---------|----------|-----------|--------------|-------------|
| Traefik | traefik.fzymgc.house | traefik | kube-vip VIP | Forward-Auth |
| Longhorn | longhorn.fzymgc.house | longhorn-system | Traefik IngressRoute | Forward-Auth |

## Applications

| Service | Hostname | Namespace | Ingress Type | Auth Method |
|---------|----------|-----------|--------------|-------------|
| Windmill | windmill.fzymgc.house | windmill | Traefik IngressRoute | OIDC |

---

*Update this table when adding or modifying services.*

Step 2: Create technologies.md (from Notion Tech References)

# Technologies

Technology stack with documentation links.

## Kubernetes

| Technology | Category | Docs | Version |
|------------|----------|------|---------|
| k3s | Kubernetes | [k3s.io](https://k3s.io/) | 1.31.x |
| Calico | Networking | [projectcalico.org](https://docs.tigera.io/calico/) | 3.29.x |
| MetalLB | Load Balancer | [metallb.io](https://metallb.io/) | 0.14.x |
| Longhorn | Storage | [longhorn.io](https://longhorn.io/docs/) | 1.7.x |
| Traefik | Ingress | [traefik.io](https://doc.traefik.io/traefik/) | 3.x |

## Security

| Technology | Category | Docs | Version |
|------------|----------|------|---------|
| Vault | Secrets | [vaultproject.io](https://developer.hashicorp.com/vault/docs) | 1.18.x |
| Authentik | SSO | [goauthentik.io](https://docs.goauthentik.io/) | 2024.x |
| cert-manager | Certificates | [cert-manager.io](https://cert-manager.io/docs/) | 1.16.x |
| External Secrets | Secrets Sync | [external-secrets.io](https://external-secrets.io/) | 0.12.x |

## Observability

| Technology | Category | Docs | Version |
|------------|----------|------|---------|
| Grafana | Dashboards | [grafana.com](https://grafana.com/docs/) | 11.x |
| VictoriaMetrics | Metrics | [victoriametrics.com](https://docs.victoriametrics.com/) | 1.x |
| Loki | Logs | [grafana.com/loki](https://grafana.com/docs/loki/) | 3.x |

## GitOps & Automation

| Technology | Category | Docs | Version |
|------------|----------|------|---------|
| ArgoCD | GitOps | [argo-cd.readthedocs.io](https://argo-cd.readthedocs.io/) | 2.13.x |
| Terraform | IaC | [terraform.io](https://developer.hashicorp.com/terraform/docs) | 1.12.x |
| HCP Terraform | Remote Execution | [cloud.hashicorp.com](https://developer.hashicorp.com/terraform/cloud-docs) | - |
| Windmill | Workflows | [windmill.dev](https://www.windmill.dev/docs/) | - |

---

*Update versions when upgrading components.*

Step 3: Create network.md

# Network Reference

IP addresses, CIDRs, and DNS configuration.

## Kubernetes Networking

| Resource | Value |
|----------|-------|
| K8s API VIP | 192.168.20.140 (kube-vip) |
| Cluster CIDR (pods) | 10.42.0.0/16 |
| Service CIDR | 10.43.0.0/16 |
| Cluster DNS | 10.43.0.10 |

## MetalLB Address Pools

| Pool | Range | Purpose |
|------|-------|---------|
| default | 192.168.20.145-149 | General services |
| reserved | 192.168.20.155-159 | Critical infrastructure |

## Node IPs

| Node | IP | Role |
|------|-----|------|
| tpi-alpha-1 | 192.168.20.101 | Control plane |
| tpi-alpha-2 | 192.168.20.102 | Control plane |
| tpi-alpha-3 | 192.168.20.103 | Control plane |
| tpi-alpha-4 | 192.168.20.104 | Worker |
| tpi-beta-1 | 192.168.20.111 | Worker |
| tpi-beta-2 | 192.168.20.112 | Worker |
| tpi-beta-3 | 192.168.20.113 | Worker |
| tpi-beta-4 | 192.168.20.114 | Worker |

## DNS

| Domain | Purpose |
|--------|---------|
| fzymgc.house | Internal services |
| fzymgc.net | External/webhook services |

Step 4: Create secrets.md

# Secrets & Vault Paths

Vault secret locations and policies.

## Secret Structure
secret/fzymgc-house/ ├── cluster/ │ ├── authentik/ # Authentik tokens │ ├── cloudflared/ # Tunnel credentials │ ├── grafana/ # Grafana secrets │ └── ... └── infrastructure/ └── bmc/ # BMC credentials
## Key Vault Paths

| Path | Purpose | Consumers |
|------|---------|-----------|
| `secret/fzymgc-house/cluster/authentik` | Authentik API token | Terraform |
| `secret/fzymgc-house/cluster/cloudflared/tunnels/*` | Tunnel credentials | cloudflared |
| `secret/fzymgc-house/cluster/grafana` | Grafana secrets | Terraform |

## Kubernetes Auth Roles

| Role | Namespace | Service Account | Policies |
|------|-----------|-----------------|----------|
| external-secrets | external-secrets | external-secrets | external-secrets |
| cert-manager | cert-manager | cert-manager | cert-manager |

---

*See `tf/vault/` for policy definitions.*

Step 5: Verify structure

Run: ls docs/reference/ Expected: index.md, services.md, technologies.md, network.md, secrets.md

Step 6: Commit

git add docs/reference/
git commit -m "feat(docs): add reference documentation"

Task 7: Create Architecture Overview

Files: - Create: docs/architecture/overview.md

Step 1: Create overview.md

# System Overview

High-level architecture of the fzymgc-house cluster.

## Hardware

Two TuringPi 2 boards (alpha/beta) with 8 RK1 compute modules:
┌─────────────────────────────────────────────────────┐ │ TuringPi Alpha │ │ ┌─────────┬─────────┬─────────┬─────────┐ │ │ │ Node 1 │ Node 2 │ Node 3 │ Node 4 │ │ │ │ Control │ Control │ Control │ Worker │ │ │ └─────────┴─────────┴─────────┴─────────┘ │ └─────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────┐ │ TuringPi Beta │ │ ┌─────────┬─────────┬─────────┬─────────┐ │ │ │ Node 1 │ Node 2 │ Node 3 │ Node 4 │ │ │ │ Worker │ Worker │ Worker │ Worker │ │ │ └─────────┴─────────┴─────────┴─────────┘ │ └─────────────────────────────────────────────────────┘
## Three-Layer Architecture

| Layer | Tool | Purpose |
|-------|------|---------|
| Infrastructure | Ansible | OS config, k3s install, node setup |
| Platform | Terraform | Vault policies, Authentik config, integrations |
| Applications | ArgoCD | Kubernetes manifests, GitOps deployments |

## Data Flow
┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Internet │────▶│ Cloudflare │────▶│ Traefik │ └──────────────┘ │ Tunnel │ │ Ingress │ └──────────────┘ └──────┬───────┘ │ ┌───────────────────────────┼───────────────────────────┐ │ ▼ │ │ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ │ │ Authentik │ │ Vault │ │ Grafana │ │ │ │ (SSO) │ │ (Secrets) │ │(Observ.) │ │ │ └──────────────┘ └──────────────┘ └───────────┘ │ │ │ │ ┌────────────────────────────────────────────────┐ │ │ │ Application Pods │ │ │ └────────────────────────────────────────────────┘ │ │ Kubernetes │ └───────────────────────────────────────────────────────┘
## GitOps Workflow

1. Changes committed to `main` branch
2. ArgoCD detects changes
3. Manifests synced to cluster
4. Applications updated automatically

Step 2: Commit

git add docs/architecture/
git commit -m "feat(docs): add architecture overview"

Task 8: Clean Up Old Docs Root

Files: - Delete: docs/README.md (replaced by index.md)

Step 1: Remove old README

rm docs/README.md

Step 2: Test build

Run: mkdocs build --strict Expected: Build succeeds

Step 3: Commit

git add docs/README.md
git commit -m "chore(docs): remove old README (replaced by index.md)"

Phase 3: CI/CD

Task 9: Create GitHub Actions Workflow

Files: - Create: .github/workflows/docs.yml

Step 1: Create workflow file

name: Deploy Docs

on:
  push:
    branches: [main]
    paths:
      - 'docs/**'
      - 'mkdocs.yml'
      - '.github/workflows/docs.yml'
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      deployments: write

    steps:
      - uses: actions/checkout@v6

      - name: Setup Python
        uses: actions/setup-python@v6
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: pip install mkdocs-material

      - name: Build docs
        run: mkdocs build --strict

      - name: Deploy to Cloudflare Pages
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy site --project-name=cluster-docs

Step 2: Verify YAML

Run: python -c "import yaml; yaml.safe_load(open('.github/workflows/docs.yml'))" Expected: No output (success)

Step 3: Commit

git add .github/workflows/docs.yml
git commit -m "feat(ci): add docs deployment workflow"

Task 10: Create Cloudflare Pages Project

Manual Steps (Cloudflare Dashboard or CLI):

  1. Go to Cloudflare Dashboard → Pages
  2. Create project named cluster-docs
  3. Set production branch to main
  4. No build settings needed (we build in GitHub Actions)

Or via Wrangler CLI:

wrangler pages project create cluster-docs --production-branch main

Step 1: Add GitHub secrets

Add to repository secrets: - CLOUDFLARE_API_TOKEN — API token with Pages edit permissions - CLOUDFLARE_ACCOUNT_ID — Your Cloudflare account ID

Step 2: Verify project exists

Run: wrangler pages project list | grep cluster-docs Expected: Project listed


Phase 4: Authentik Exposure

Task 11: Update Tunnel Configuration

Files: - Modify: tf/cloudflare/variables.tf - Modify: tf/cloudflare/tunnel.tf

Step 1: Add authentik_services variable to variables.tf

Add after webhook_services variable:

variable "authentik_services" {
  description = "Authentik endpoints to expose via tunnel"
  type = map(object({
    service_url = string
  }))
  default = {
    auth = {
      service_url = "https://authentik-server.authentik.svc.cluster.local"
    }
  }
}

Step 2: Update tunnel.tf ingress

Modify the cloudflare_zero_trust_tunnel_cloudflared_config resource, update the ingress concat:

config = {
  ingress = concat(
    # Webhook services (existing)
    [for svc, definition in var.webhook_services : {
      hostname = "${svc}${var.webhook_suffix}.${var.webhook_domain}"
      service  = definition.service_url
      origin_request = {
        http_host_header   = "${svc}${var.webhook_suffix}.${var.webhook_domain}"
        origin_server_name = split("//", definition.service_url)[1]
        no_tls_verify      = false
      }
    }],
    # Authentik services
    [for svc, definition in var.authentik_services : {
      hostname = "${svc}.fzymgc.house"
      service  = definition.service_url
      origin_request = {
        http_host_header   = "${svc}.fzymgc.house"
        origin_server_name = "authentik-server.authentik.svc.cluster.local"
        no_tls_verify      = true
      }
    }],
    # Catch-all
    [{ service = "http_status:404" }]
  )
}

Step 3: Add Authentik DNS record

Add to tunnel.tf:

resource "cloudflare_dns_record" "authentik" {
  for_each = var.authentik_services

  zone_id = data.cloudflare_zone.fzymgc_house.id
  name    = each.key
  content = "${cloudflare_zero_trust_tunnel_cloudflared.main.id}.cfargotunnel.com"
  type    = "CNAME"
  proxied = true
  comment = "Authentik via ${var.tunnel_name} tunnel"
  ttl     = 1
}

Step 4: Verify Terraform

Run: terraform -chdir=tf/cloudflare validate Expected: Success

Step 5: Commit

git add tf/cloudflare/
git commit -m "feat(cloudflare): expose Authentik via tunnel"

Task 12: Create Authentik OIDC Application

Files: - Modify: tf/authentik/ — Add Cloudflare Access OIDC provider

Step 1: Add OIDC provider and application

Create or modify appropriate file in tf/authentik/:

# Cloudflare Access OIDC Provider
resource "authentik_provider_oauth2" "cloudflare_access" {
  name                  = "Cloudflare Access"
  client_id             = random_string.cloudflare_access_client_id.result
  authorization_flow    = data.authentik_flow.default_authorization.id
  invalidation_flow     = data.authentik_flow.default_invalidation.id
  client_type           = "confidential"
  signing_key           = data.authentik_certificate_key_pair.default.id

  access_token_validity = "minutes=5"

  property_mappings = [
    data.authentik_property_mapping_provider_scope.openid.id,
    data.authentik_property_mapping_provider_scope.email.id,
    data.authentik_property_mapping_provider_scope.profile.id,
  ]

  redirect_uris = [
    "https://*.cloudflareaccess.com/cdn-cgi/access/callback",
  ]
}

resource "random_string" "cloudflare_access_client_id" {
  length  = 32
  special = false
}

resource "random_password" "cloudflare_access_client_secret" {
  length  = 64
  special = false
}

# Store credentials in Vault
resource "vault_kv_secret_v2" "cloudflare_access_oidc" {
  mount = "secret"
  name  = "fzymgc-house/cluster/cloudflare-access/authentik"

  data_json = jsonencode({
    client_id     = random_string.cloudflare_access_client_id.result
    client_secret = random_password.cloudflare_access_client_secret.result
  })
}

# Application
resource "authentik_application" "cloudflare_access" {
  name              = "Cloudflare Access"
  slug              = "cloudflare-access"
  protocol_provider = authentik_provider_oauth2.cloudflare_access.id
  meta_launch_url   = "https://docs.fzymgc.house/"
}

Step 2: Commit

git add tf/authentik/
git commit -m "feat(authentik): add Cloudflare Access OIDC provider"

Phase 5: Access Protection

Task 13: Create Access Configuration

Files: - Create: tf/cloudflare/access.tf - Modify: tf/cloudflare/variables.tf

Step 1: Add variables for IDPs

Add to tf/cloudflare/variables.tf:

variable "authentik_oidc_client_id" {
  description = "Authentik OIDC client ID for Cloudflare Access"
  type        = string
  sensitive   = true
}

variable "authentik_oidc_client_secret" {
  description = "Authentik OIDC client secret for Cloudflare Access"
  type        = string
  sensitive   = true
}

variable "github_oauth_client_id" {
  description = "GitHub OAuth App client ID"
  type        = string
  sensitive   = true
}

variable "github_oauth_client_secret" {
  description = "GitHub OAuth App client secret"
  type        = string
  sensitive   = true
}

Step 2: Create access.tf

# ==============================================================================
# Identity Providers
# ==============================================================================

resource "cloudflare_zero_trust_access_identity_provider" "authentik" {
  account_id = var.cloudflare_account_id
  name       = "Authentik"
  type       = "oidc"

  config {
    client_id     = var.authentik_oidc_client_id
    client_secret = var.authentik_oidc_client_secret
    auth_url      = "https://auth.fzymgc.house/application/o/authorize/"
    token_url     = "https://auth.fzymgc.house/application/o/token/"
    certs_url     = "https://auth.fzymgc.house/application/o/cloudflare-access/jwks/"
    scopes        = ["openid", "email", "profile"]
  }
}

resource "cloudflare_zero_trust_access_identity_provider" "github" {
  account_id = var.cloudflare_account_id
  name       = "GitHub"
  type       = "github"

  config {
    client_id     = var.github_oauth_client_id
    client_secret = var.github_oauth_client_secret
  }
}

resource "cloudflare_zero_trust_access_identity_provider" "otp" {
  account_id = var.cloudflare_account_id
  name       = "Email One-Time PIN"
  type       = "otp"
}

# ==============================================================================
# Cluster Docs Application
# ==============================================================================

resource "cloudflare_zero_trust_access_application" "cluster_docs" {
  account_id       = var.cloudflare_account_id
  name             = "Cluster Documentation"
  domain           = "docs.fzymgc.house"
  type             = "self_hosted"
  session_duration = "24h"

  allowed_idps = [
    cloudflare_zero_trust_access_identity_provider.authentik.id,
    cloudflare_zero_trust_access_identity_provider.github.id,
    cloudflare_zero_trust_access_identity_provider.otp.id,
  ]
}

resource "cloudflare_zero_trust_access_policy" "cluster_docs_allow" {
  account_id     = var.cloudflare_account_id
  application_id = cloudflare_zero_trust_access_application.cluster_docs.id
  name           = "Allow authorized users"
  precedence     = 1
  decision       = "allow"

  include {
    login_method = [
      cloudflare_zero_trust_access_identity_provider.authentik.id,
      cloudflare_zero_trust_access_identity_provider.github.id,
      cloudflare_zero_trust_access_identity_provider.otp.id,
    ]
  }

  require {
    email_domain = [
      "fzymgc.email",
      "fzymgc.house",
      "fzymgc.dev",
      "fuzzymagic.com",
    ]
  }
}

Step 3: Validate

Run: terraform -chdir=tf/cloudflare validate Expected: Success

Step 4: Commit

git add tf/cloudflare/
git commit -m "feat(cloudflare): add Access protection for docs site"

Task 14: Create GitHub OAuth App

Manual Steps:

  1. Go to GitHub → Settings → Developer settings → OAuth Apps
  2. Create new OAuth App:
  3. Name: fzymgc-house Cloudflare Access
  4. Homepage URL: https://docs.fzymgc.house
  5. Callback URL: https://fzymgc-house.cloudflareaccess.com/cdn-cgi/access/callback
  6. Note client ID and generate client secret

Step 1: Store in Vault

vault kv put secret/fzymgc-house/cluster/cloudflare-access/github \
  client_id="<github_client_id>" \
  client_secret="<github_client_secret>"

Phase 6: Cleanup

Task 15: Slim Down CLAUDE.md Files

Files: - Modify: CLAUDE.md (root)

Step 1: Update root CLAUDE.md

Remove duplicated content, add pointers to docs. Example update:

Add to appropriate section:

## Documentation

**Primary documentation:** https://docs.fzymgc.house (or run `mkdocs serve` locally)

See `docs/` for:
- [Operations guides](docs/operations/) — Vault, Authentik, Cloudflare procedures
- [Reference](docs/reference/) — Services catalog, network info, secrets
- [Architecture](docs/architecture/) — System design and decisions

Step 2: Commit

git add CLAUDE.md
git commit -m "docs: update CLAUDE.md to reference docs site"

Task 16: Update Notion References

Step 1: Update any files referencing Notion

Search for Notion links and update to point to new docs site or mark as deprecated.

Run: rg -l "notion.so" docs/

Step 2: Commit updates

git add -A
git commit -m "docs: deprecate Notion references in favor of docs site"

Task 17: Final Verification

Step 1: Full local build

Run: mkdocs build --strict Expected: Build succeeds with no errors

Step 2: Local preview

Run: mkdocs serve Expected: All pages render, navigation works, search works

Step 3: Push to trigger deployment

git push origin main

Step 4: Verify GitHub Action

Check: https://github.com/fzymgc-house/selfhosted-cluster/actions Expected: Workflow runs and deploys successfully

Step 5: Verify Access protection

Open: https://docs.fzymgc.house Expected: Redirected to Cloudflare Access login, shows 3 IDP options

Step 6: Test each IDP

  • [ ] Authentik login works
  • [ ] GitHub login works
  • [ ] OTP login works
  • [ ] Unauthorized email domain is rejected

Success Criteria Checklist

  • [ ] MkDocs builds successfully with --strict
  • [ ] Full-text search works across all docs
  • [ ] Navigation reflects new structure
  • [ ] Authentik exposed via Cloudflare Tunnel
  • [ ] GitHub OAuth App configured
  • [ ] All 3 IDPs functional in Access
  • [ ] Email domain restriction enforced
  • [ ] Auto-deploy on push to main works
  • [ ] Existing doc content migrated
  • [ ] Reference docs created from Notion data
  • [ ] CLAUDE.md files updated with pointers