Phase 02: First Spoke Repository

Phase 2: Create the First Spoke Repository

A spoke is a self-contained documentation component. It has its own antora.yml, its own pages, partials, examples, and its own attribute inventory. The critical design principle: spokes are independently deployable — you can clone a single spoke and build it without the hub or any other spoke. This means every attribute a spoke uses must be defined in its own antora.yml, even if other spokes define the same value.

Architecture Decision: docs/asciidoc Start Path

Technical spokes use docs/asciidoc/ as the Antora start path. This gives the repo room for non-Antora content (scripts, CI/CD, README) in docs/ without polluting the Antora module structure.

infra-docs/
├── docs/
│   ├── asciidoc/               ← Antora starts HERE (start_path in playbook)
│   │   ├── antora.yml          ← Component descriptor
│   │   └── modules/ROOT/       ← Content lives here
│   └── other/                  ← Non-Antora docs, scripts, etc.
├── scripts/                    ← Operational scripts
├── README.adoc
└── .gitignore

Step 1: Initialize the Spoke

mkdir infra-docs && cd infra-docs
git init
mkdir -p docs/asciidoc/modules/ROOT/{pages,partials,examples,images}
mkdir -p docs/asciidoc/modules/ROOT/pages/{runbooks,architecture,reference}
mkdir -p docs/asciidoc/modules/ROOT/partials/{runbooks,common}
mkdir -p docs/asciidoc/modules/ROOT/examples/{scripts,configs}

Step 2: Create the Component Descriptor

This is the spoke’s identity. The attribute section is the most important part — it defines every mutable value the documentation references.

File: docs/asciidoc/antora.yml

name: infra-ops
title: Infrastructure Operations
version: ~
start_page: ROOT:index.adoc
nav:
  - modules/ROOT/nav.adoc
asciidoc:
  attributes:
    # ================================================================
    # NAVIGATION & METADATA
    # ================================================================
    nav_order: '10'
    category: Infrastructure
    doc-id: PRJ-INFRA-OPS
    author: Your Name
    icons: font
    source-highlighter: highlight.js
    experimental: ''

    # ================================================================
    # DOMAIN CONFIGURATION
    # ================================================================
    domain: corp.example.com

    # ================================================================
    # INFRASTRUCTURE HOSTS — HOSTNAMES
    # ================================================================
    # Naming convention: <device>-hostname
    sw-core-01-hostname: sw-core-01.corp.example.com
    sw-core-02-hostname: sw-core-02.corp.example.com
    sw-dist-01-hostname: sw-dist-01.corp.example.com
    fw-01-hostname: fw-01.corp.example.com
    wlc-01-hostname: wlc-01.corp.example.com
    ise-01-hostname: ise-01.corp.example.com
    ise-02-hostname: ise-02.corp.example.com
    dc-01-hostname: dc-01.corp.example.com

    # ================================================================
    # INFRASTRUCTURE HOSTS — IP ADDRESSES
    # ================================================================
    # Naming convention: <device>-ip
    sw-core-01-ip: 10.1.1.1
    sw-core-02-ip: 10.1.1.2
    sw-dist-01-ip: 10.1.1.10
    fw-01-ip: 10.1.1.20
    wlc-01-ip: 10.1.1.30
    ise-01-ip: 10.1.10.20
    ise-02-ip: 10.1.10.21
    dc-01-ip: 10.1.10.50

    # ================================================================
    # NETWORK — VLANs
    # ================================================================
    # Naming convention: vlan-<name>
    vlan-mgmt: 10
    vlan-data: 20
    vlan-voice: 30
    vlan-iot: 40
    vlan-guest: 50
    vlan-quarantine: 999

    # ================================================================
    # NETWORK — SUBNETS & GATEWAYS
    # ================================================================
    subnet-mgmt: 10.1.10.0/24
    subnet-data: 10.1.20.0/24
    subnet-voice: 10.1.30.0/24
    subnet-iot: 10.1.40.0/24
    gateway-mgmt: 10.1.10.1
    gateway-data: 10.1.20.1

    # ================================================================
    # PROTOCOLS & PORTS
    # ================================================================
    port-radius-auth: 1812
    port-radius-acct: 1813
    port-tacacs: 49
    port-snmp: 161
    port-syslog: 514
    port-ntp: 123
    port-ssh: 22
    port-https: 443
    port-dns: 53

    # ================================================================
    # FILESYSTEM PATHS
    # ================================================================
    cert-dir: /etc/ssl/certs
    key-dir: /etc/ssl/private
    backup-dir: /opt/backups

    # ================================================================
    # ISE CONFIGURATION (if applicable)
    # ================================================================
    # policy-set-wired: Wired_802.1X
    # authz-profile-corp: PermitAccess
    # dacl-quarantine: ACL-QUARANTINE

    # ================================================================
    # PERSONNEL
    # ================================================================
    person-lead: NetworkLead
    team-name: Network Engineering

Why duplicate attributes across spokes? Each spoke is independently buildable. If nac-docs also references ise-01-ip, it defines it in its own antora.yml. When the IP changes, you update every spoke — but each spoke can be built, tested, and deployed independently. This is the same pattern used across the domus-digitalis 15-spoke federation.

Step 3: Create the Navigation

File: docs/asciidoc/modules/ROOT/nav.adoc

* xref:index.adoc[Overview]
* Runbooks
** xref:runbooks/switch-upgrade.adoc[Switch Upgrade]
** xref:runbooks/firewall-change.adoc[Firewall Change]
* Architecture
** xref:architecture/network-topology.adoc[Network Topology]
** xref:architecture/vlan-design.adoc[VLAN Design]
* Reference
** xref:reference/vlan-table.adoc[VLAN Table]
** xref:reference/ip-allocation.adoc[IP Allocation]

As the spoke grows beyond 50 pages, switch to the modular nav partial pattern (see Phase 8 and Appendix: Nav Patterns).

Step 4: Create the Landing Page

File: docs/asciidoc/modules/ROOT/pages/index.adoc

= Infrastructure Operations
:description: Runbooks, architecture, and reference for network infrastructure
:icons: font

Operations documentation for the {team-name} team.

== Quick Links

* xref:runbooks/switch-upgrade.adoc[Switch Upgrade Procedure]
* xref:architecture/network-topology.adoc[Network Topology]
* xref:reference/vlan-table.adoc[VLAN Reference Table]

== Infrastructure Summary

[cols="2,2,2"]
|===
| System | IP | Status

| Core Switch 1
| {sw-core-01-ip}
| Production

| Core Switch 2
| {sw-core-02-ip}
| Production

| ISE Primary
| {ise-01-ip}
| Production

| ISE Secondary
| {ise-02-ip}
| Production
|===

Note: every IP comes from an attribute. No hardcoded values.

Step 5: Create a Reusable Partial

File: docs/asciidoc/modules/ROOT/partials/common/change-control-note.adoc

[IMPORTANT]
====
All production changes require a Change Request (CR) with:

* Pre-verification commands and expected output
* Change commands (copy-paste ready)
* Post-verification commands and expected output
* Rollback procedure

Follow the change control process before modifying production systems.
====

Use in any page:

include::partial$common/change-control-note.adoc[]

Step 6: Create a Sample Runbook with Attributes

File: docs/asciidoc/modules/ROOT/pages/runbooks/switch-upgrade.adoc

= Switch Upgrade Procedure
:description: IOS-XE upgrade procedure for Catalyst switches
:icons: font

include::partial$common/change-control-note.adoc[]

== Pre-Verification

[source,bash,subs=attributes+]
....
ssh admin@{sw-core-01-ip}
show version | include System image
show boot
show flash: | include .bin
....

== Upgrade Steps

[source,bash,subs=attributes+]
....
ssh admin@{sw-core-01-ip}
copy tftp://{fw-01-ip}/cat9k_iosxe.17.09.05.SPA.bin flash:
install add file flash:cat9k_iosxe.17.09.05.SPA.bin activate commit
....

== Post-Verification

[source,bash,subs=attributes+]
....
ssh admin@{sw-core-01-ip}
show version | include System image
show ip interface brief | include up
show spanning-tree summary
....

== Rollback

[source,bash,subs=attributes+]
....
ssh admin@{sw-core-01-ip}
install rollback to committed
....

Every command block uses subs=attributes+ so that {sw-core-01-ip} renders as the actual IP. Change the IP in antora.yml once — every runbook updates.

Step 7: Register in the Hub Playbook

In team-docs/antora-playbook.yml:

    # Infrastructure Operations
    - url: https://github.com/your-org/infra-docs
      branches: main
      start_path: docs/asciidoc
      edit_url: false

For local development:

    - url: ../infra-docs
      branches: HEAD
      start_path: docs/asciidoc

Step 8: Build and Verify

cd ../team-docs
make check
make serve
# Navigate to http://localhost:8080/infra-ops/

Directory Structure After Phase 2

infra-docs/
├── docs/
│   └── asciidoc/                           ← start_path
│       ├── antora.yml                      # 80+ attributes
│       └── modules/ROOT/
│           ├── nav.adoc                    # Component navigation
│           ├── pages/
│           │   ├── index.adoc              # Landing page (uses attributes)
│           │   ├── runbooks/
│           │   │   ├── switch-upgrade.adoc # Uses {sw-core-01-ip}
│           │   │   └── firewall-change.adoc
│           │   ├── architecture/
│           │   │   ├── network-topology.adoc
│           │   │   └── vlan-design.adoc
│           │   └── reference/
│           │       ├── vlan-table.adoc     # Uses {vlan-data}, {vlan-voice}
│           │       └── ip-allocation.adoc
│           ├── partials/
│           │   ├── common/
│           │   │   └── change-control-note.adoc
│           │   └── runbooks/
│           ├── examples/
│           │   ├── scripts/
│           │   └── configs/
│           └── images/
│               └── diagrams/
└── .gitignore