This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
site/
|
||||
.cache/
|
||||
.venv/
|
||||
__pycache__/
|
||||
@@ -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
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
# Hardware
|
||||
|
||||
Physical inventory.
|
||||
|
||||
| Item | Model | Serial | Purchased | Warranty | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| Gateway | UniFi UDR7 | _redact on public_ | | | |
|
||||
| _add yours_ | | | | | |
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
+70
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user