From baf674b1be44e0e081666a7062b88223d433031d Mon Sep 17 00:00:00 2001 From: John Date: Wed, 16 Sep 2026 11:42:32 -0400 Subject: [PATCH] first commit and build --- .gitea/workflows/deploy.yml | 21 +++++++++ .gitignore | 4 ++ README.md | 24 ++++++++++ docs/hardware/index.md | 8 ++++ docs/index.md | 17 ++++++++ docs/network/firewall.md | 21 +++++++++ docs/network/index.md | 8 ++++ docs/network/topology.md | 27 ++++++++++++ docs/network/vlans.md | 18 ++++++++ docs/runbooks/disaster-recovery.md | 45 +++++++++++++++++++ docs/runbooks/index.md | 6 +++ docs/services/index.md | 12 +++++ mkdocs.yml | 70 ++++++++++++++++++++++++++++++ requirements.txt | 4 ++ 14 files changed, 285 insertions(+) create mode 100644 .gitea/workflows/deploy.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 docs/hardware/index.md create mode 100644 docs/index.md create mode 100644 docs/network/firewall.md create mode 100644 docs/network/index.md create mode 100644 docs/network/topology.md create mode 100644 docs/network/vlans.md create mode 100644 docs/runbooks/disaster-recovery.md create mode 100644 docs/runbooks/index.md create mode 100644 docs/services/index.md create mode 100644 mkdocs.yml create mode 100644 requirements.txt diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..50dc943 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,21 @@ +name: build-and-deploy +on: + push: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + container: catthehacker/ubuntu:act-latest + steps: + - name: Checkout (internal — bypasses Cloudflare) + run: | + git clone --depth 1 --branch "${{ github.ref_name }}" \ + --config http.extraheader="Authorization: token ${{ github.token }}" \ + "http://gitea:3000/${{ github.repository }}.git" . + + - name: Install MkDocs Material + run: pip install --break-system-packages --ignore-installed -r requirements.txt + + - name: Build and deploy + run: mkdocs build --strict --clean -d /srv/www/docs-public \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f97e65b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +site/ +.cache/ +.venv/ +__pycache__/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..77d61dc --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# Homelab Docs (private / full runbook) + +Source of truth for the Frostlabs homelab. Plain Markdown, rendered with +MkDocs Material, built and deployed by Gitea Actions on every push to `main`. + +- **Edit** files under `docs/`, `git push`, done. CI builds and deploys. +- **Live site:** https://runbook.frostlabs.me (gated by Cloudflare Access) +- **Public portfolio subset:** separate repo → https://docs.frostlabs.me + +## Local preview +```bash +python -m venv .venv && . .venv/bin/activate +pip install -r requirements.txt +mkdocs serve # http://127.0.0.1:8000 +``` + +## Repo layout +``` +docs/ + network/ topology, VLANs, firewall <- first real content + services/ what runs where + runbooks/ step-by-step recovery procedures + hardware/ physical inventory +``` diff --git a/docs/hardware/index.md b/docs/hardware/index.md new file mode 100644 index 0000000..aa48701 --- /dev/null +++ b/docs/hardware/index.md @@ -0,0 +1,8 @@ +# Hardware + +Physical inventory. + +| Item | Model | Serial | Purchased | Warranty | Notes | +|---|---|---|---|---|---| +| Gateway | UniFi UDR7 | _redact on public_ | | | | +| _add yours_ | | | | | | diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..34981e0 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,17 @@ +# Frostlabs Homelab + +Operational documentation and disaster-recovery runbooks. + +!!! info "How this site works" + The source of truth is the Markdown in this repository on `git.frostlabs.me`. + On every push to `main`, Gitea Actions rebuilds and redeploys this site. The + rendered page you're reading is disposable — if it's ever lost, it rebuilds + from the repo. The repo itself lives in three independent places (Gitea VPS, + homelab, laptop), which is what makes this survive a total system failure. + +## Start here + +- [Network](network/index.md) — topology, VLAN/subnet plan, firewall policy +- [Runbooks](runbooks/index.md) — step-by-step recovery procedures +- [Services](services/index.md) — what runs where +- [Hardware](hardware/index.md) — physical inventory diff --git a/docs/network/firewall.md b/docs/network/firewall.md new file mode 100644 index 0000000..15572ea --- /dev/null +++ b/docs/network/firewall.md @@ -0,0 +1,21 @@ +# Firewall + +Zone-based policy on the UDR7. Document the **intent** first, then the rules +that implement it — future-you (mid-outage) needs the reasoning, not just a +rule dump. + +## Policy matrix + +| From \ To | Management | Trusted | IoT | Guest | WAN | +|---|---|---|---|---|---| +| Management | — | allow | allow | allow | allow | +| Trusted | deny | — | allow | deny | allow | +| IoT | deny | deny | — | deny | allow* | +| Guest | deny | deny | deny | — | allow | + +\* IoT to WAN often restricted to specific destinations. Document exceptions. + +## Rationale + +Explain each non-obvious allow/deny: why IoT can't reach Trusted, which +management exceptions exist, and any per-device carve-outs. diff --git a/docs/network/index.md b/docs/network/index.md new file mode 100644 index 0000000..bf5baff --- /dev/null +++ b/docs/network/index.md @@ -0,0 +1,8 @@ +# Network + +The homelab runs on a UniFi UDR7 with a segmented VLAN design and a +zone-based firewall policy. This section is the reference for that design. + +- [Topology](topology.md) — physical + logical layout +- [VLANs & Subnets](vlans.md) — the segmentation plan +- [Firewall](firewall.md) — inter-zone policy and rationale diff --git a/docs/network/topology.md b/docs/network/topology.md new file mode 100644 index 0000000..69e6693 --- /dev/null +++ b/docs/network/topology.md @@ -0,0 +1,27 @@ +# Topology + +!!! note "Template" + The diagram below is a scaffold. Replace the nodes/edges with your actual + layout. It renders automatically — Material draws ` ```mermaid ` blocks. + +```mermaid +graph TD + WAN[Internet] --> UDR7[UniFi UDR7] + UDR7 --> SW[Aggregation Switch] + SW --> AP[Access Points] + SW --> VLAN_MGMT[VLAN 10 · Management] + SW --> VLAN_TRUST[VLAN 20 · Trusted] + SW --> VLAN_IOT[VLAN 30 · IoT] + SW --> VLAN_GUEST[VLAN 40 · Guest] +``` + +## Physical + +| Device | Model | Location | Notes | +|---|---|---|---| +| Gateway | UniFi UDR7 | _rack_ | _replace with real detail_ | +| Switch | _model_ | _location_ | _uplinks / PoE_ | + +## Logical + +Describe uplinks, tagged/untagged ports, and where each VLAN is trunked. diff --git a/docs/network/vlans.md b/docs/network/vlans.md new file mode 100644 index 0000000..dea5145 --- /dev/null +++ b/docs/network/vlans.md @@ -0,0 +1,18 @@ +# VLANs & Subnets + +!!! warning "Fill in real values" + The rows below are an **example format**, not your live plan. Replace them + with your actual VLAN IDs, subnets, and gateways. On the private site this + is fine; keep the [public portfolio version](../index.md) sanitized. + +| VLAN | Name | Subnet | Gateway | DHCP | Purpose | +|---|---|---|---|---|---| +| 10 | Management | 10.0.10.0/24 | 10.0.10.1 | reserved | Switches, APs, UDR7 UI | +| 20 | Trusted | 10.0.20.0/24 | 10.0.20.1 | yes | Workstations, phones | +| 30 | IoT | 10.0.30.0/24 | 10.0.30.1 | yes | Smart-home, Matter/Thread | +| 40 | Guest | 10.0.40.0/24 | 10.0.40.1 | yes | Isolated guest access | + +## Notes + +- Reserved (static) leases: document them per device. +- mDNS / service discovery across VLANs: note where it's allowed and how. diff --git a/docs/runbooks/disaster-recovery.md b/docs/runbooks/disaster-recovery.md new file mode 100644 index 0000000..ae7ab9e --- /dev/null +++ b/docs/runbooks/disaster-recovery.md @@ -0,0 +1,45 @@ +# Disaster Recovery: Git + Docs stack + +Goal: restore `git.frostlabs.me` and the docs sites after losing the VPS. + +## What state actually matters + +| Thing | Where | Rebuildable? | +|---|---|---| +| Gitea data (repos, DB, config) | `/opt/gitea/gitea/` (SQLite + repos) | **No — back this up** | +| Runner registration | `/opt/gitea/runner/.runner` | No (but re-registerable in minutes) | +| Built sites | `/srv/www/` | Yes — rebuilt on next push | +| Tunnel / nginx / compose config | `/opt/gitea/*.yml`, `.env`, `nginx.conf` | Keep a copy off-box | + +The docs repos themselves are safe as long as one clone survives (VPS, homelab, +laptop). This runbook is about restoring the **server**. + +## Backup (do this on a schedule) + +```bash +# Off-box snapshot of everything stateful on the VPS +rsync -aP /opt/gitea/ backup-target:/backups/gitea/ +``` + +!!! tip + A `gitea dump` is the "proper" way, but for a single-user SQLite instance an + rsync of `/opt/gitea` while the stack is stopped is simplest and complete. + +## Restore onto a fresh VPS + +1. Provision the box, install Docker + compose. +2. Recreate `/opt/gitea/` and restore the backup into it. +3. Recreate `.env` with `TUNNEL_TOKEN` and (optionally) `RUNNER_TOKEN`. +4. `mkdir -p /srv/www/docs-public /srv/www/docs-private` +5. `docker compose up -d` +6. In Cloudflare Zero Trust, point the tunnel at the new box (token is the same + tunnel, so hostnames just reattach). Confirm `git.frostlabs.me` loads. +7. If the runner didn't reconnect, re-register it (Site Admin → Actions → Runners). +8. Push any repo (or hit **Run workflow**) to rebuild both sites into `/srv/www`. + +## Verify + +- [ ] `git.frostlabs.me` reachable, repos present +- [ ] `runbook.frostlabs.me` rebuilt and gated by CF Access +- [ ] `docs.frostlabs.me` rebuilt and public +- [ ] a test push triggers a green Actions run diff --git a/docs/runbooks/index.md b/docs/runbooks/index.md new file mode 100644 index 0000000..faab976 --- /dev/null +++ b/docs/runbooks/index.md @@ -0,0 +1,6 @@ +# Runbooks + +Step-by-step procedures for when something is on fire. Written to be followed +under stress — imperative steps, exact commands, no ambiguity. + +- [Disaster Recovery](disaster-recovery.md) — rebuild the docs/Git stack from nothing diff --git a/docs/services/index.md b/docs/services/index.md new file mode 100644 index 0000000..b29cf8d --- /dev/null +++ b/docs/services/index.md @@ -0,0 +1,12 @@ +# Services + +What runs where, and how to reach it. + +| Service | Host | VLAN | URL / Port | Notes | +|---|---|---|---|---| +| Gitea | VPS (Linode) | — | git.frostlabs.me | via Cloudflare Tunnel | +| Docs (this site) | VPS (Linode) | — | runbook.frostlabs.me | nginx + CF Access | +| _add yours_ | | | | | + +For each service, link to its own page with config location, backup method, +and restore steps. diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..3fa87f5 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,70 @@ +site_name: Frostlabs Homelab +site_url: https://docs.frostlabs.me/ +site_description: Homelab operational documentation and disaster-recovery runbooks. + +theme: + name: material + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/weather-night + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/weather-sunny + name: Switch to light mode + features: + - navigation.instant + - navigation.tracking + - navigation.sections + - navigation.indexes + - navigation.top + - toc.follow + - search.suggest + - search.highlight + - content.code.copy + - content.code.annotate + +markdown_extensions: + - admonition + - attr_list + - md_in_html + - tables + - toc: + permalink: true + - pymdownx.details + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.tabbed: + alternate_style: true + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + +plugins: + - search + +nav: + - Home: index.md + - Network: + - network/index.md + - Topology: network/topology.md + - VLANs & Subnets: network/vlans.md + - Firewall: network/firewall.md + - Services: + - services/index.md + - Runbooks: + - runbooks/index.md + - Disaster Recovery: runbooks/disaster-recovery.md + - Hardware: + - hardware/index.md diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2eddc28 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +# Pinned to the Material 9.x line (EOL 2026-11-05, security fixes until then). +# When you migrate the renderer later (Zensical / MkDocs 2.0), only this file +# and .gitea/workflows/deploy.yml change — the docs/ Markdown is untouched. +mkdocs-material>=9.7,<10