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