phase: 05_update_projects_section
Build and Push Container / build-and-push (push) Successful in 16s

**Phase 05 final pass — all 4 tasks already complete; verified phase-level criteria + docs. Repo 100% green.**

- Verified: 6 `project-card fade-in` cards; order Brain of Reese → Reverse Proxy → Dynamic DNS → Local AI Stack → Deployment Automation → ServWaf; `src/index.html` diff hunks (lines 293–538) all inside `#projects` (286–542).
- `./build.sh` → `Build complete: dist/` (exit 0); `npm test` → **48 passed, 0 failed**; `bash .agents/validate.sh` → **exit 0** (48 E2E + `HEADER CHECK PASSED` on `/`, `/index.html`, hashed CSS via podman + repo-hygiene gates). No linter exists in this repo (no `lint` script/eslint config). Coverage: no JS/CSS touched — vacuous per phase spec; E2E is the binding gate.
- Completion criteria: card count 6 in `src/` **and** `dist/` ✅ · first card "Brain of Reese" with `…/Experimental/brain-of-reese` href in `dist/index.html` ✅ · `grep -c "Self-Hosted Services\|Kubernetes Cluster" src/index.html` = **0** ✅ · AI card has vLLM/Gufo/llama.cpp + LocalAI/LiteLLM/Langfuse, no "Ollama" ✅ · regression test present in `tests/e2e/01-page.spec.js` and passing ✅ · `./build.sh && npm test` green (48 ≥ 45) ✅ · no edits outside `#projects` ✅ · README/AGENTS consistent ✅.
- Docs: README **updated** (new paragraph stating what the E2E suite covers, incl. the six featured cards, so future content edits update the matching test). AGENTS.md **updated** (convention: the `#projects` grid is test-locked by `01-page.spec.js`). All other README claims re-checked against reality (Dockerfile builds `dist/` internally, `listen 8080`, snippet `include`s, `check-headers.sh` port/paths/output, `--bind 0.0.0.0` config, Node ≥20 engine floor, CI `main`+tag publishing) — consistent.
- Notable: `.agents/validate.sh` is still **untracked** though AGENTS.md/README reference it — harness should `git add` it (I ran no git index commands). `.agents/PLAN.md` §4/§5 still say "44 tests" (suite is 48 since Phases 04/05); PLAN.md is off-limits, so this drift needs an explicit user-approved edit. No live-infrastructure touched.
- Next pending phase: none in `.agents/phases/todo/` (05 was last). Remaining unphased audit items (L-3 CSP `unsafe-inline`, L-5 SSH-key labelling, L-7 CI pinning, L-8 build.sh CI check) would need new phases via `phase-authoring`.
This commit is contained in:
2026-10-23 01:32:52 -04:00
parent 2f6f2dc0b9
commit 1fe932cf39
28 changed files with 2454 additions and 93 deletions
+79 -93
View File
@@ -290,6 +290,48 @@
A selection of projects from my homelab and deployment infrastructure.
</p>
<div class="projects-grid" role="list" aria-label="Projects">
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-comment"># Hybrid search: pgvector cosine ∪ Postgres full-text</span></div>
<div class="code-line"><span class="code-keyword">def</span> <span class="code-function">retrieve</span>(<span class="code-variable">query</span>, <span class="code-variable">k</span>=<span class="code-variable">5</span>):</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">vec</span> = <span class="code-function">pgvector</span>.<span
class="code-function">cosine</span>(<span class="code-variable">query</span>)</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">fts</span> = <span class="code-function">postgres</span>.<span
class="code-function">full_text</span>(<span class="code-variable">query</span>)</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">chunks</span> = <span class="code-function">rrf_fuse</span>(<span class="code-variable">vec</span>, <span class="code-variable">fts</span>,
<span class="code-variable">k</span>)</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-keyword">return</span> <span class="code-function">llm</span>.<span
class="code-function">chat</span>(<span class="code-function">document</span>(<span class="code-variable">chunks</span>))</div>
<div class="code-line">&nbsp;</div>
</div>
</div>
<div class="project-info">
<h3>Brain of Reese</h3>
<p>A chippy, honest RAG chatbot over homelab documentation — git repositories, local
directories, and uploaded archives. Hybrid search (pgvector cosine ∪ Postgres
full-text search, Reciprocal Rank Fusion) feeds the whole relevant document to a
self-hosted LLM and streams back a grounded answer; when it has no notes it
admits it and suggests questions it can answer.</p>
<div class="project-meta">
<div class="project-tech">
<span>FastAPI</span>
<span>pgvector</span>
<span>Postgres</span>
<span>Playwright</span>
</div>
<div class="project-links">
<a href="https://gitea.reeseapps.com/Experimental/brain-of-reese" title="Source" target="_blank"
rel="noopener">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
</svg>
</a>
</div>
</div>
</div>
</div>
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
@@ -382,32 +424,26 @@
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-keyword">from</span> <span
class="code-string">ollama</span> <span class="code-keyword">import</span> <span
class="code-function">Client</span></div>
<div class="code-line"><span class="code-comment"># Inference engines</span></div>
<div class="code-line"><span class="code-variable">engines</span> = {</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-string">"vllm"</span>: <span class="code-string">"cuda"</span>, <span class="code-comment"># high-throughput serving</span></div>
<div class="code-line">&nbsp;&nbsp;<span class="code-string">"gufo"</span>: <span class="code-string">"rocm"</span>, <span class="code-comment"># Strix Halo inference</span></div>
<div class="code-line">&nbsp;&nbsp;<span class="code-string">"llama.cpp"</span>: <span class="code-string">"gguf"</span>, <span class="code-comment"># CPU/GPU fallback</span></div>
<div class="code-line">}</div>
<div class="code-line">&nbsp;</div>
<div class="code-line"><span class="code-variable">client</span> = <span
class="code-function">Client</span>(<span
class="code-string">"http://localhost:11434"</span>)</div>
<div class="code-line"><span class="code-variable">response</span> = <span
class="code-variable">client</span>.<span class="code-function">chat</span>(</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">model</span>=<span
class="code-string">"llama3"</span>,</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">messages</span>=[...]</div>
<div class="code-line">)</div>
<div class="code-line">&nbsp;</div>
<div class="code-line"><span class="code-comment"># LiteLLM proxy for unified API</span>
</div>
<div class="code-line"><span class="code-comment"># LiteLLM proxy for unified API</span></div>
</div>
</div>
<div class="project-info">
<h3>Local AI Stack</h3>
<p>Complete local AI infrastructure: Ollama and LocalAI for inference, LiteLLM as a unified API
proxy, Bifrost for model routing, and Langfuse for observability. Supports both CUDA and
ROCm.</p>
<p>Complete local AI infrastructure: vLLM, Gufo, and llama.cpp for inference, LiteLLM as a
unified API proxy, Bifrost for model routing, and Langfuse for observability. Supports
both CUDA and ROCm.</p>
<div class="project-meta">
<div class="project-tech">
<span>Ollama</span>
<span>vLLM</span>
<span>Gufo</span>
<span>llama.cpp</span>
<span>LocalAI</span>
<span>LiteLLM</span>
<span>Langfuse</span>
@@ -469,85 +505,35 @@
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-comment"># Self-hosted services</span></div>
<div class="code-line"><span class="code-keyword">- Immich</span> <span
class="code-comment"># Photo/video management</span></div>
<div class="code-line"><span class="code-keyword">- Jellyfin</span> <span
class="code-comment"># Media streaming</span></div>
<div class="code-line"><span class="code-keyword">- Nextcloud</span> <span
class="code-comment"># Cloud storage & sync</span></div>
<div class="code-line"><span class="code-keyword">- Gitea</span> <span
class="code-comment"># Git service</span></div>
<div class="code-line"><span class="code-keyword">- Matrix</span> <span
class="code-comment"># Chat protocol</span></div>
<div class="code-line"><span class="code-keyword">- Home Assistant</span> <span
class="code-comment"># Smart home</span></div>
<div class="code-line"><span class="code-keyword">- Pi-hole</span> <span
class="code-comment"># DNS ad blocking</span></div>
<div class="code-line"><span class="code-comment"># ServWaf — declarative rules</span></div>
<div class="code-line"><span class="code-variable">ratelimit</span>:</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">per_ip</span>: <span
class="code-string">20 rps, burst 40</span></div>
<div class="code-line"><span class="code-variable">rules</span>:</div>
<div class="code-line">&nbsp;&nbsp;- <span class="code-keyword">deny</span> <span
class="code-variable">if</span> <span class="code-variable">user_agent</span> =~
<span class="code-string">"curl|wget"</span></div>
<div class="code-line">&nbsp;&nbsp;- <span class="code-keyword">flag</span> <span
class="code-variable">if</span> <span class="code-variable">body</span> =~ <span
class="code-string">"(?i)union\s+select"</span></div>
<div class="code-line"><span class="code-variable">ai</span>:</div>
<div class="code-line">&nbsp;&nbsp;<span class="code-variable">endpoint</span>: <span
class="code-string">"http://aipi.reeseapps.com/v1"</span></div>
</div>
</div>
<div class="project-info">
<h3>Self-Hosted Services</h3>
<p>A diverse fleet of self-hosted services: Immich for photos, Jellyfin for media, Nextcloud for
storage, Matrix for chat, Home Assistant for IoT, and more. Each running as rootless Podman
containers with SELinux awareness.</p>
<h3>ServWaf</h3>
<p>A network Web Application Firewall written in Go, sitting in line between clients
and the origin. Declarative YAML rules (user agent, IP/CIDR, URL and body
regexes), per-IP rate limiting, attack-pattern inspection (SQL injection, XSS,
path traversal, CRLF, scanner probes), opt-in self-hosted local-AI verdicts,
TLS termination, and an opt-in HTTP/3 (QUIC) edge.</p>
<div class="project-meta">
<div class="project-tech">
<span>Immich</span>
<span>Jellyfin</span>
<span>Nextcloud</span>
<span>Matrix</span>
</div>
<div class="project-links">
<a href="https://gitea.reeseapps.com/services/homelab" title="Source" target="_blank"
rel="noopener">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
</svg>
</a>
</div>
</div>
</div>
</div>
<div class="project-card fade-in" role="listitem">
<div class="project-preview">
<div class="code-block">
<div class="code-line"><span class="code-comment"># Kubernetes with k3s</span></div>
<div class="code-line"><span class="code-keyword">- metallb</span> <span
class="code-comment"># L2 load balancer</span></div>
<div class="code-line"><span class="code-keyword">- longhorn</span> <span
class="code-comment"># Distributed storage</span></div>
<div class="code-line"><span class="code-keyword">- traefik</span> <span
class="code-comment"># Ingress gateway</span></div>
<div class="code-line"><span class="code-keyword">- external-dns</span> <span
class="code-comment"># Route53 integration</span></div>
<div class="code-line"><span class="code-keyword">- grafana</span> <span
class="code-comment"># Metrics dashboards</span></div>
<div class="code-line"><span class="code-keyword">- minecraft</span> <span
class="code-comment"># Game server</span></div>
</div>
</div>
<div class="project-info">
<h3>Kubernetes Cluster</h3>
<p>k3s and k0s Kubernetes clusters with MetalLB for L2 failover, Longhorn for distributed
storage, Traefik/Nginx ingress, cert-manager with Route53 DNS challenge, and Helm charts for
service deployment.</p>
<div class="project-meta">
<div class="project-tech">
<span>k3s</span>
<span>Kubernetes</span>
<span>Helm</span>
<span>MetalLB</span>
</div>
<div class="project-links">
<a href="https://gitea.reeseapps.com/services/homelab" title="Source" target="_blank"
rel="noopener">
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
<path
d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z" />
</svg>
</a>
<span>Go</span>
<span>YAML</span>
<span>HTTP/3</span>
<span>Podman</span>
</div>
</div>
</div>