Author SHA1 Message Date
venus 70134d2e00 added more samples and identified ideal candidates 2026-09-12 22:47:46 -05:00
18 changed files with 11044 additions and 2 deletions
File diff suppressed because it is too large Load Diff
+780
View File
@@ -0,0 +1,780 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Blog Lander (Tiling Hero &amp; Feed)</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Tile surface */
--bg-elevated: #1c1f26; /* Hovered tile surface */
--border-color: #2b303b; /* Crisp 1px solid border */
--text-primary: #e0e6f0; /* Light off-white prose */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Primary active accent */
--accent-green: #00e676; /* Operational status indicator */
--accent-amber: #ffb700; /* Warning / highlight accent */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px 48px 16px;
}
.tiling-layout {
width: 100%;
max-width: 1120px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Top Global Status Header Bar (Gateway 3 Style) */
.global-bar {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px 18px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
flex-wrap: wrap;
gap: 10px;
}
.brand-group {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
font-weight: 700;
}
.status-indicator {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
}
.bar-nav {
display: flex;
gap: 14px;
align-items: center;
}
.bar-link {
color: var(--text-muted);
text-decoration: none;
transition: none; /* Instant snap */
}
.bar-link:hover {
color: var(--accent-cyan);
}
.bar-link.active {
color: var(--text-primary);
border-bottom: 1px solid var(--accent-cyan);
}
/* Hero Tile: Section Declaration & Synopsis */
.hero-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-left: 3px solid var(--accent-cyan);
border-radius: 4px;
padding: 24px 28px;
display: flex;
flex-direction: column;
gap: 12px;
}
.hero-pretitle {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--accent-cyan);
letter-spacing: 0.06em;
text-transform: uppercase;
}
.hero-main-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 2.1rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
line-height: 1.2;
}
.hero-landing-text {
font-size: 0.98rem;
color: var(--text-primary);
line-height: 1.65;
max-width: 82ch;
}
.filter-pills-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-top: 4px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
}
.filter-tag-btn {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
color: var(--text-muted);
padding: 3px 8px;
border-radius: 2px;
cursor: pointer;
transition: none;
}
.filter-tag-btn:hover {
border-color: var(--accent-cyan);
color: var(--text-primary);
background-color: var(--bg-elevated);
}
.filter-tag-btn.active {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
border-color: var(--accent-cyan);
font-weight: 700;
}
/* Asymmetric Split Grid */
.split-grid {
display: grid;
grid-template-columns: 1.25fr 0.75fr;
gap: 16px;
}
@media (max-width: 880px) {
.split-grid {
grid-template-columns: 1fr;
}
}
/* Standard Tile Container */
.tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 14px;
}
.tile-header {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 1px solid var(--border-color);
padding-bottom: 10px;
}
.tile-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.92rem;
font-weight: 700;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.tile-meta {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
}
/* Featured Lead Article Card (Inside Tile) */
.featured-card-body {
display: flex;
flex-direction: column;
gap: 12px;
}
.featured-badge-row {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
flex-wrap: wrap;
gap: 8px;
}
.badge-pinned {
color: var(--accent-amber);
font-weight: 700;
}
.featured-article-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.35rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
line-height: 1.3;
transition: none;
}
.featured-article-title:hover {
color: var(--accent-cyan);
}
.featured-prose {
font-size: 0.92rem;
color: var(--text-primary);
line-height: 1.6;
}
/* Terminal Code Block Component */
.terminal-code-box {
border: 1px solid var(--border-color);
border-radius: 3px;
background-color: var(--bg-canvas);
overflow: hidden;
font-family: 'JetBrains Mono', monospace;
}
.code-titlebar {
background-color: var(--bg-surface);
border-bottom: 1px solid var(--border-color);
padding: 6px 12px;
font-size: 0.74rem;
color: var(--text-muted);
display: flex;
justify-content: space-between;
}
.code-content {
padding: 12px;
font-size: 0.78rem;
overflow-x: auto;
color: var(--text-primary);
line-height: 1.5;
}
.featured-cta-row {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10px;
border-top: 1px solid var(--border-color);
flex-wrap: wrap;
gap: 10px;
font-family: 'IBM Plex Mono', monospace;
}
.cta-button {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
padding: 6px 14px;
font-weight: 700;
font-size: 0.8rem;
text-decoration: none;
border-radius: 2px;
border: 1px solid var(--accent-cyan);
transition: none;
}
.cta-button:hover {
background-color: transparent;
color: var(--accent-cyan);
}
/* Sidebar Tile Elements */
.sidebar-stats-box {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 12px 14px;
display: flex;
flex-direction: column;
gap: 8px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
}
.stat-row {
display: flex;
justify-content: space-between;
}
.stat-val {
color: var(--text-primary);
font-weight: 700;
}
.topic-track-item {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 4px;
text-decoration: none;
transition: none;
}
.topic-track-item:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.topic-track-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.84rem;
font-weight: 700;
color: var(--text-primary);
}
.topic-track-item:hover .topic-track-title {
color: var(--accent-cyan);
}
.topic-track-desc {
font-size: 0.78rem;
color: var(--text-muted);
line-height: 1.4;
}
/* Full-Width Chronological Article Feed Tile */
.feed-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.feed-card {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 16px 18px;
display: flex;
flex-direction: column;
gap: 8px;
transition: none;
}
.feed-card:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.feed-meta-row {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
color: var(--text-muted);
}
.feed-date {
color: var(--accent-cyan);
}
.feed-heading {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.1rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
line-height: 1.35;
transition: none;
}
.feed-card:hover .feed-heading {
color: var(--accent-cyan);
}
.feed-excerpt {
font-size: 0.9rem;
color: var(--text-primary);
line-height: 1.6;
}
.feed-tags-row {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 6px;
border-top: 1px solid var(--border-color);
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 6px;
}
.tag-cluster {
display: flex;
gap: 8px;
}
/* Global Footer Tile (Gateway 3 Standard) */
.footer-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 10px;
}
.footer-links-cluster {
display: flex;
gap: 12px;
align-items: center;
}
.footer-btn {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-btn:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="tiling-layout">
<!-- Top Global Status Bar (Gateway 3 Match) -->
<header class="global-bar">
<div class="brand-group">
<span class="status-indicator"></span>
<span>RIVER ROOKS // BLOG &amp; RESEARCH ARCHIVE</span>
</div>
<nav class="bar-nav" aria-label="Quick Navigation">
<a href="service-gateway-tiling.html" class="bar-link">Gateway 3 ↗</a>
<a href="#" class="bar-link active">&gt; Dispatches</a>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="bar-link">Git Forge</a>
<a href="feed.xml" class="bar-link">RSS Feed</a>
<a href="index.html" class="bar-link">Catalog ↗</a>
</nav>
</header>
<!-- Hero Tile: Blog Mission & Topic Filters -->
<section class="hero-tile" aria-label="Blog Mission & Overview">
<div class="hero-pretitle">TECHNICAL PUBLICATION REPOSITORY &bull; ~/NOTES/INDEX.MD</div>
<h1 class="hero-main-title">Engineering Notes, CTF Write-Ups &amp; Systems Architecture</h1>
<p class="hero-landing-text">
Documenting bare-metal systems, embedded hardware audits, and network isolation architectures. Writing zero-allocation POSIX scripts, reverse engineering production microcontrollers, and analyzing memory allocator exploitation primitives.
</p>
<div class="filter-pills-row">
<span style="color: var(--text-muted); margin-right: 4px;">TOPIC FILTER:</span>
<button class="filter-tag-btn active" onclick="filterTopic('all', this)">[All Dispatches (5)]</button>
<button class="filter-tag-btn" onclick="filterTopic('firmware', this)">[#firmware (1)]</button>
<button class="filter-tag-btn" onclick="filterTopic('homelab', this)">[#homelab (2)]</button>
<button class="filter-tag-btn" onclick="filterTopic('security', this)">[#security (3)]</button>
<button class="filter-tag-btn" onclick="filterTopic('posix-sh', this)">[#posix-sh (1)]</button>
</div>
</section>
<!-- Split Grid: Featured Article + Research Index -->
<div class="split-grid">
<!-- Left Column: Featured Article Tile -->
<section class="tile" aria-label="Featured Technical Article">
<div class="tile-header">
<div class="tile-title">
<span>// PINNED RESEARCH WRITE-UP</span>
</div>
<span class="tile-meta">SPI FLASH &bull; 8 MIN READ</span>
</div>
<div class="featured-card-body">
<div class="featured-badge-row">
<span class="badge-pinned">[FEATURED DEEP DIVE]</span>
<span style="color: var(--text-muted);">2026-07-20 &bull; ~/notes/iot-firmware-spi-extraction.md</span>
</div>
<h2 style="margin: 0;">
<a href="article-tiling-firmware.html" class="featured-article-title">Reverse Engineering IoT Firmware: SPI Flash Extraction</a>
</h2>
<p class="featured-prose">
In security audits of embedded systems, retrieving firmware is the critical first step. When over-the-air updates are encrypted and debug ports (UART, JTAG) are disabled in production silicon, dumping physical SPI Flash memory becomes necessary. This writeup covers extracting firmware from an 8-pin SOP SPI flash chip using a Bus Pirate and flashrom to bypass bootloader lockdowns.
</p>
<!-- Terminal Code Box -->
<div class="terminal-code-box">
<div class="code-titlebar">
<span>extract_firmware.sh</span>
<span>POSIX sh</span>
</div>
<div class="code-content">
<pre><code># Query the SPI flash details using flashrom via Bus Pirate
flashrom -p buspirate_spi:dev=/dev/ttyUSB0,spispeed=1M -r firmware_dump.bin
# Verify the integrity of the binary dump by comparing hashes
if [ $? -eq 0 ]; then
echo "Dump successful. Running md5sum..."
md5sum firmware_dump.bin
fi</code></pre>
</div>
</div>
<div class="featured-cta-row">
<div class="tag-cluster">
<span style="color: var(--text-muted); font-size: 0.75rem;">[#firmware] [#hardware] [#security] [#bus-pirate]</span>
</div>
<a href="article-tiling-firmware.html" class="cta-button">READ FULL WRITEUP &rarr;</a>
</div>
</div>
</section>
<!-- Right Column: Quick Research Tracks & Telemetry -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<!-- Publication Stats Tile -->
<section class="tile" aria-label="Archive Statistics">
<div class="tile-header">
<div class="tile-title">
<span>// ARCHIVE TELEMETRY</span>
</div>
<span class="tile-meta">STATIC HTML</span>
</div>
<div class="sidebar-stats-box">
<div class="stat-row">
<span>TOTAL ARTICLES:</span>
<span class="stat-val">5 PUBLISHED</span>
</div>
<div class="stat-row">
<span>TOTAL READING TIME:</span>
<span class="stat-val">51 MINUTES</span>
</div>
<div class="stat-row">
<span>ACTIVE TOPICS:</span>
<span class="stat-val">7 CATEGORIES</span>
</div>
<div class="stat-row">
<span>FORMAT COMPLIANCE:</span>
<span class="stat-val">IEEE STD 1003.1</span>
</div>
</div>
</section>
<!-- Curated Focus Tracks -->
<section class="tile" aria-label="Curated Topic Tracks">
<div class="tile-header">
<div class="tile-title">
<span>// RESEARCH TRACKS</span>
</div>
<span class="tile-meta">CURATED</span>
</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
<a href="#" class="topic-track-item">
<div class="topic-track-title">&gt; Embedded &amp; Hardware Forensics</div>
<div class="topic-track-desc">Flash memory dumping, bus tapping, silicon pinout discovery.</div>
</a>
<a href="#" class="topic-track-item">
<div class="topic-track-title">&gt; Network Routing &amp; Homelab</div>
<div class="topic-track-desc">Traefik reverse proxying, WireGuard full-mesh, VLAN firewalls.</div>
</a>
<a href="#" class="topic-track-item">
<div class="topic-track-title">&gt; Low-Level Memory Exploitation</div>
<div class="topic-track-desc">glibc tcache safe-linking, heap corruption, House of Apple.</div>
</a>
</div>
</section>
</div>
</div>
<!-- Full-Width Article Feed Tile -->
<section class="tile" aria-label="Chronological Article Stream">
<div class="tile-header">
<div class="tile-title">
<span>// CHRONOLOGICAL DISPATCHES FEED</span>
</div>
<span class="tile-meta">ALL ARTICLES ORDERED BY DATE</span>
</div>
<div class="feed-list" id="feedList">
<!-- Post 2: Traefik -->
<article class="feed-card" data-tags="homelab networking">
<div class="feed-meta-row">
<time datetime="2026-07-15" class="feed-date">2026-07-15 &bull; 12 min read</time>
<span>~/notes/ha-traefik-docker.md</span>
</div>
<h3 style="margin: 0;">
<a href="article-tiling-traefik.html" class="feed-heading">&gt; Building a High-Availability Traefik Router with Docker Compose</a>
</h3>
<p class="feed-excerpt">
Architecting automated TLS wildcard certificate propagation, Docker provider label routing, zero-downtime container updates, and backend health-check failover routes on bare-metal infrastructure.
</p>
<div class="feed-tags-row">
<div class="tag-cluster">
<span>[#homelab]</span>
<span>[#docker-traefik]</span>
<span>[#networking]</span>
</div>
<a href="article-tiling-traefik.html" style="color: var(--accent-cyan); text-decoration: none;">Read Entry &rarr;</a>
</div>
</article>
<!-- Post 3: POSIX sh -->
<article class="feed-card" data-tags="posix-sh security">
<div class="feed-meta-row">
<time datetime="2026-07-10" class="feed-date">2026-07-10 &bull; 6 min read</time>
<span>~/notes/posix-sh-vs-bash.md</span>
</div>
<h3 style="margin: 0;">
<a href="#" class="feed-heading">&gt; POSIX sh vs. Bash: Portability in Modern Scripts</a>
</h3>
<p class="feed-excerpt">
Analyzing edge-case incompatibilities, syntax overhead, eliminating bashisms, and writing fast, bulletproof bootstrapping scripts for Alpine and embedded targets conforming to IEEE Std 1003.1.
</p>
<div class="feed-tags-row">
<div class="tag-cluster">
<span>[#posix-sh]</span>
<span>[#shell]</span>
<span>[#unix]</span>
</div>
<a href="#" style="color: var(--accent-cyan); text-decoration: none;">Read Entry &rarr;</a>
</div>
</article>
<!-- Post 4: Heap Exploitation -->
<article class="feed-card" data-tags="security">
<div class="feed-meta-row">
<time datetime="2026-06-28" class="feed-date">2026-06-28 &bull; 15 min read</time>
<span>~/notes/ctf-heap-glibc-2-35.md</span>
</div>
<h3 style="margin: 0;">
<a href="article-tiling-heap.html" class="feed-heading">&gt; CTF Write-up: Heap Exploitation in glibc 2.35</a>
</h3>
<p class="feed-excerpt">
Deconstructing modern glibc memory allocator defenses: tcache safe-linking pointer mangling, bypass primitives, and House of Apple file structure hijacking for arbitrary code execution.
</p>
<div class="feed-tags-row">
<div class="tag-cluster">
<span>[#ctf]</span>
<span>[#binary-exploitation]</span>
<span>[#security]</span>
</div>
<a href="article-tiling-heap.html" style="color: var(--accent-cyan); text-decoration: none;">Read Entry &rarr;</a>
</div>
</article>
<!-- Post 5: Zero-Trust WireGuard -->
<article class="feed-card" data-tags="networking security homelab">
<div class="feed-meta-row">
<time datetime="2026-06-14" class="feed-date">2026-06-14 &bull; 10 min read</time>
<span>~/notes/zero-trust-wireguard-opnsense.md</span>
</div>
<h3 style="margin: 0;">
<a href="#" class="feed-heading">&gt; Zero-Trust Network Isolation with WireGuard &amp; OPNsense VLANs</a>
</h3>
<p class="feed-excerpt">
Designing strict zone-based firewall rules across isolated VLANs (Management, Server DMZ, IoT, Trusted Clients) with point-to-point WireGuard cryptographic routing.
</p>
<div class="feed-tags-row">
<div class="tag-cluster">
<span>[#networking]</span>
<span>[#wireguard]</span>
<span>[#security]</span>
</div>
<a href="#" style="color: var(--accent-cyan); text-decoration: none;">Read Entry &rarr;</a>
</div>
</article>
</div>
</section>
<!-- Global Footer Tile (Matching Gateway 3 Exactly) -->
<footer class="footer-tile">
<div>River Rooks &bull; University of Alabama College of Engineering</div>
<nav class="footer-links-cluster" aria-label="Global Footer Links">
<a href="service-gateway-tiling.html" class="footer-btn">Gateway Lander</a>
<span class="pipe">|</span>
<a href="feed.xml" class="footer-btn">RSS</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-btn">Forgejo</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-btn">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="#" title="Fingerprint: 0x4A8E92F1C0B3D4E7" class="footer-btn">PGP</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-btn">Catalog ↗</a>
</nav>
</footer>
</main>
<script>
function filterTopic(topic, btn) {
document.querySelectorAll('.filter-tag-btn').forEach(el => el.classList.remove('active'));
btn.classList.add('active');
const cards = document.querySelectorAll('#feedList .feed-card');
cards.forEach(card => {
if (topic === 'all') {
card.style.display = 'flex';
} else {
const tags = card.getAttribute('data-tags') || '';
if (tags.includes(topic)) {
card.style.display = 'flex';
} else {
card.style.display = 'none';
}
}
});
}
</script>
</body>
</html>
+81
View File
@@ -0,0 +1,81 @@
# Project Brief: Technical Web Architecture & Blog
**Project Type:** Personal Blog & Technical Documentation Site
**Design Philosophy:** High-signal, low-noise. Developer-centric minimalism.
**Vibe/Comparisons:** Zed Editor, Ghostty, Noctalia.dev.
**Strictly Avoid:** Raycast, Astro, Vercel-style "SaaS" aesthetics.
## 1. Executive Summary
This project requires a web interface that feels like a native development environment or terminal emulator, not a marketing page. The design must prioritize raw performance, keyboard navigability, and information density. It should evoke the craftsmanship of tiling window managers and modal text editors.
The content will heavily feature deep technical writing, including CTF write-ups, network routing diagrams, Docker/Traefik architectures, and raw code snippets (Lua, POSIX `sh`, and Python). The design must accommodate high-density information without visual clutter.
---
## 2. Global Rules & Anti-Patterns
To achieve the correct aesthetic, the designer must adhere to strict constraints.
> **The "Do Not Use" List**
> * **NO Glassmorphism:** Zero use of `backdrop-filter: blur()`, frosted glass, or translucent overlays.
> * **NO Glows or Drop Shadows:** Elements should not float, glow, or cast shadows. Depth is created through crisp 1px borders and distinct surface colors.
> * **NO Gradients:** Avoid sweeping linear or radial gradients. Stick to solid colors.
> * **NO Spring Animations:** Avoid floaty, delayed transitions.
> * **NO Rounded Blobs:** Keep border-radii minimal (0px to 4px max). Sharp corners are preferred.
---
## 3. Typography System
The typographical hierarchy is a hybrid system utilizing monospace fonts for structural elements and a clean sans-serif for long-form reading.
| Usage | Font Family Preference | Weight | Role |
| :--- | :--- | :--- | :--- |
| **Structural / UI** | IBM Plex Mono, JetBrains Mono | Regular (400), Medium (500) | Navigation links, metadata, dates, tags, timestamps, buttons. |
| **Code Blocks** | JetBrains Mono, Fira Code | Regular (400) | Inline code snippets, terminal command blocks, scripts. |
| **Body Prose** | Inter, Geist Sans, Roboto | Regular (400) | Standard article paragraphs, lists. |
| **Headings (H1-H3)**| IBM Plex Mono (or Sans variant) | Bold (700) | Article titles, section headers. |
**Typographical Constraints:**
* **Line Height:** `1.6` to `1.7` for prose.
* **Max Width:** Cap article text at `65ch` (characters) for optimal readability.
---
## 4. Color Palette (Dark Mode Native)
The site does not need a light mode. It should be built as a dark-mode native experience using a muted, low-saturation charcoal palette, mimicking a modern terminal or IDE.
* **Base Canvas (`#121417` or `#0d0f12`):** A deep slate/charcoal. Not pure black (`#000000`), which causes eye strain.
* **Surface / Card (`#1c1f26`):** Slightly lighter than the canvas for article blocks or terminal windows.
* **Borders & Dividers (`#2b303b`):** Subtle, low-contrast 1px solid lines used to define grids and separate content.
* **Primary Text (`#e0e6f0`):** Off-white/light ash for prose.
* **Muted Text (`#8a95a5`):** For timestamps, metadata, and non-essential UI text.
* **Accent Color (Designer's Choice):** One single, vibrant terminal color (e.g., ANSI Green, Amber, or Cyan) used sparingly for active states, link hovers, or terminal prompts (like `>`).
---
## 5. Layout & Component Architecture
The layout should be heavily grid-based, feeling structural and deliberate.
### Core Components Needed
1. **Terminal-Style Code Blocks:** Must support syntax highlighting. Should include a simple header (e.g., showing the filename like `config.lua` or `docker-compose.yml`) with a 1px border.
2. **Metadata Tags:** Small, monospaced inline blocks for categorizing posts (e.g., `[#homelab]`, `[#ctf]`, `[#hyprland]`).
3. **Keyboard Shortcut Hints:** Visual styling for `<kbd>` elements, rendering as crisp, bordered boxes (e.g., `Ctrl` + `K`).
4. **Index/Archive List:** A dense, tabular list for older posts, prioritized by date, title, and tags, rather than large image thumbnails.
### Spacing & Grid
* Use a rigid 4pt or 8pt baseline grid.
* Padding inside code blocks and cards should be dense but mathematically consistent.
* Full-bleed horizontal dividers (`<hr>`) should be used to separate major sections instead of large margins.
---
## 6. Interaction & Motion
Interactivity should mirror command-line interfaces: fast, binary, and unembellished.
* **Hover States:** Immediate state changes. Instead of fading a button's background over `300ms`, snap the background color instantly or invert the text/background colors.
* **Focus States:** Explicit, high-visibility 1px or 2px solid outlines (using the accent color) when navigating via the `Tab` key. Accessibility and keyboard navigation are paramount.
* **Active Elements:** Use structural indicators (like a `>` prepended to an active navigation link, or a solid left-border block) rather than changing font sizes or scaling elements up.
+592
View File
@@ -0,0 +1,592 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Homelab Gateway (Tiling Workspace)</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Tile surface */
--bg-elevated: #1c1f26; /* Hovered tile */
--border-color: #2b303b; /* Crisp 1px border */
--text-primary: #e0e6f0; /* Prose off-white */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Primary active accent */
--accent-green: #00e676; /* Online indicator */
--accent-amber: #ffb700; /* Warning / internal indicator */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px 48px 16px;
}
.tiling-layout {
width: 100%;
max-width: 1120px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Top Global Status Header Bar */
.global-bar {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px 18px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
flex-wrap: wrap;
gap: 10px;
}
.brand-group {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
font-weight: 700;
}
.status-indicator {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
}
.bar-nav {
display: flex;
gap: 14px;
align-items: center;
}
.bar-link {
color: var(--text-muted);
text-decoration: none;
transition: none; /* Instant snap */
}
.bar-link:hover {
color: var(--accent-cyan);
}
/* Primary Hero Landing Tile (Main Focus of Homepage) */
.hero-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-left: 3px solid var(--accent-cyan);
border-radius: 4px;
padding: 28px 32px;
display: flex;
flex-direction: column;
gap: 16px;
}
.hero-pretitle {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--accent-cyan);
letter-spacing: 0.06em;
text-transform: uppercase;
}
.hero-main-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 2.2rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
line-height: 1.2;
}
.hero-landing-text {
font-size: 1.02rem;
color: var(--text-primary);
line-height: 1.7;
max-width: 80ch;
}
.hero-landing-text p {
margin-bottom: 14px;
}
.hero-landing-text p:last-child {
margin-bottom: 0;
}
.hero-directive-box {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 14px 18px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.84rem;
color: var(--text-muted);
line-height: 1.6;
}
.hero-directive-box strong {
color: var(--text-primary);
}
/* Tiling Grid Section: Split Columns */
.split-grid {
display: grid;
grid-template-columns: 1.15fr 0.85fr;
gap: 16px;
}
@media (max-width: 880px) {
.split-grid {
grid-template-columns: 1fr;
}
}
/* Tile Container Style */
.tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 14px;
}
.tile-header {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 1px solid var(--border-color);
padding-bottom: 10px;
}
.tile-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.95rem;
font-weight: 700;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.tile-meta {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
}
/* Featured Service Tile: The Blog */
.blog-service-feature {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
.blog-feature-top {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
}
.badge-core {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
padding: 2px 6px;
font-weight: 700;
border-radius: 2px;
}
.blog-feature-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.15rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
display: inline-block;
transition: none;
}
.blog-feature-title:hover {
color: var(--accent-cyan);
}
.blog-feature-desc {
font-size: 0.9rem;
color: var(--text-primary);
line-height: 1.5;
}
.blog-sublinks {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 4px;
border-top: 1px solid var(--border-color);
padding-top: 10px;
}
.blog-sublink-item {
display: flex;
justify-content: space-between;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--text-primary);
text-decoration: none;
padding: 4px 6px;
border-radius: 2px;
transition: none;
}
.blog-sublink-item:hover {
background-color: var(--bg-elevated);
color: var(--accent-cyan);
}
/* Homelab Services Grid */
.services-mini-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
@media (max-width: 540px) {
.services-mini-grid {
grid-template-columns: 1fr;
}
}
.service-tile-item {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 12px;
text-decoration: none;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 6px;
transition: none;
}
.service-tile-item:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.service-item-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.88rem;
font-weight: 700;
color: var(--text-primary);
}
.service-tile-item:hover .service-item-title {
color: var(--accent-cyan);
}
.service-item-tag {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.7rem;
color: var(--text-muted);
}
.service-item-desc {
font-size: 0.8rem;
color: var(--text-muted);
line-height: 1.4;
}
/* Infrastructure Nodes Telemetry Tile */
.node-stat-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
}
.node-name {
color: var(--text-primary);
font-weight: 700;
}
.node-load {
color: var(--accent-green);
}
/* Bottom Global Footer Tile */
.footer-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 10px;
}
.footer-links-cluster {
display: flex;
gap: 12px;
align-items: center;
}
.footer-btn {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-btn:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="tiling-layout">
<!-- Top Status Bar -->
<header class="global-bar">
<div class="brand-group">
<span class="status-indicator"></span>
<span>RIVER ROOKS // NODE GATEWAY</span>
</div>
<nav class="bar-nav" aria-label="Quick Nav">
<a href="blog-lander-cards.html" class="bar-link">&gt; Blog &amp; Notes</a>
<a href="homelab.html" class="bar-link">Infrastructure</a>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="bar-link">Git Forge</a>
<a href="index.html" class="bar-link">Catalog ↗</a>
</nav>
</header>
<!-- Main Hero Landing Tile (Central Focus of Homepage) -->
<section class="hero-tile" aria-label="Homepage Mission & Landing Declaration">
<div class="hero-pretitle">SYSTEM DECLARATION &bull; RACK-01 NODE GATEWAY</div>
<h1 class="hero-main-title">Engineering Sovereign Infrastructure &amp; Technical Notes</h1>
<div class="hero-landing-text">
<p>
I am River Rooks, a cybersecurity engineering student at the University of Alabama. My work centers on network forensics, self-hosted system architecture, low-level firmware exploitation, and zero-allocation Unix utilities.
</p>
<p>
This website functions as the unified frontend for my physical homelab cluster. Rather than hosting projects on opaque third-party clouds, everything here runs on bare metal in my personal server rack—orchestrated through Proxmox VE, partitioned across isolated VLANs via OPNsense, and protected by point-to-point WireGuard cryptographic meshes.
</p>
<p>
<strong>Navigation Guide:</strong> If you are here to read technical research, the <strong>Engineering Blog</strong> is linked below with deep write-ups on firmware dumping, heap exploitation, and container routing. If you are here to use tools, the <strong>Public Services</strong> provide locally hosted instances of CyberChef, Stirling PDF, and Git.
</p>
</div>
<div class="hero-directive-box">
<strong>PRIMARY DESTINATIONS:</strong>
<span style="color: var(--accent-cyan); margin-left: 8px;">[1] Technical Blog (5 Articles)</span> &bull;
<span>[2] Forgejo Git Repos</span> &bull;
<span>[3] Public Cryptographic &amp; PDF Tools</span> &bull;
<span>[4] Cluster Telemetry</span>
</div>
</section>
<!-- Split Tiling Columns -->
<div class="split-grid">
<!-- Left Column: Featured Service (Blog) + Recent Articles -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<!-- Featured Service Tile: The Blog -->
<section class="tile" aria-label="Featured Service: Technical Blog">
<div class="tile-header">
<div class="tile-title">
<span>// PRIMARY SERVICE: THE TECHNICAL BLOG</span>
</div>
<span class="tile-meta">~/notes/ &bull; 5 ARTICLES</span>
</div>
<div class="blog-service-feature">
<div class="blog-feature-top">
<span class="badge-core">[CORE PUBLICATION SERVICE]</span>
<span style="color: var(--text-muted);">2026-07-20 LAST SYNC</span>
</div>
<a href="blog-lander-cards.html" class="blog-feature-title">&gt; River Rooks Technical Notes &amp; Research</a>
<p class="blog-feature-desc">
In-depth security post-mortems, CTF write-ups, shell portability benchmarks, and bare-metal homelab architecture guides.
</p>
<div class="blog-sublinks">
<a href="blog-lander-cards.html" class="blog-sublink-item">
<span>1. Reverse Engineering IoT Firmware (SPI Flash)</span>
<span style="color: var(--accent-cyan);">[8 min] &rarr;</span>
</a>
<a href="blog-lander-cards.html" class="blog-sublink-item">
<span>2. High-Availability Traefik with Docker Compose</span>
<span style="color: var(--accent-cyan);">[12 min] &rarr;</span>
</a>
<a href="blog-lander-cards.html" class="blog-sublink-item">
<span>3. POSIX sh vs. Bash: Portability in Modern Scripts</span>
<span style="color: var(--accent-cyan);">[6 min] &rarr;</span>
</a>
<a href="blog-lander-cards.html" class="blog-sublink-item">
<span>4. CTF Write-up: Heap Exploitation in glibc 2.35</span>
<span style="color: var(--accent-cyan);">[15 min] &rarr;</span>
</a>
</div>
</div>
</section>
</div>
<!-- Right Column: Homelab Hosted Services & Node Telemetry -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<!-- Hosted Services Tile -->
<section class="tile" aria-label="Homelab Services Directory">
<div class="tile-header">
<div class="tile-title">
<span>// HOMELAB SERVICES ROUTER</span>
</div>
<span class="tile-meta">4 PUBLIC TOOLS</span>
</div>
<div class="services-mini-grid">
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="service-tile-item">
<div>
<div class="service-item-title">Forgejo Git</div>
<div class="service-item-tag">git.rooks.dev:2222</div>
</div>
<p class="service-item-desc">Self-hosted code repositories &amp; dotfiles.</p>
</a>
<a href="https://chef.rooks.dev" target="_blank" rel="noopener" class="service-tile-item">
<div>
<div class="service-item-title">CyberChef</div>
<div class="service-item-tag">chef.rooks.dev:443</div>
</div>
<p class="service-item-desc">Hex decoding, cryptography &amp; forensics.</p>
</a>
<a href="https://pdf.rooks.dev" target="_blank" rel="noopener" class="service-tile-item">
<div>
<div class="service-item-title">Stirling PDF</div>
<div class="service-item-tag">pdf.rooks.dev:443</div>
</div>
<p class="service-item-desc">Private document OCR and manipulation.</p>
</a>
<a href="https://status.rooks.dev" target="_blank" rel="noopener" class="service-tile-item">
<div>
<div class="service-item-title">Uptime Kuma</div>
<div class="service-item-tag">status.rooks.dev:3001</div>
</div>
<p class="service-item-desc">Live synthetic latency &amp; uptime telemetry.</p>
</a>
</div>
</section>
<!-- Bare-Metal Node Telemetry Tile -->
<section class="tile" aria-label="Physical Hardware Nodes">
<div class="tile-header">
<div class="tile-title">
<span>// HARDWARE NODES (RACK-01)</span>
</div>
<span class="tile-meta">10GbE SFP+</span>
</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
<div class="node-stat-row">
<div>
<span class="node-name">gw-node01.lan</span>
<span style="color: var(--text-muted); font-size: 0.72rem; margin-left: 6px;">OPNsense 24.7</span>
</div>
<span class="node-load">CPU: 18% | RAM: 32%</span>
</div>
<div class="node-stat-row">
<div>
<span class="node-name">pve-node02.lan</span>
<span style="color: var(--text-muted); font-size: 0.72rem; margin-left: 6px;">Proxmox VE (AMD 5900X)</span>
</div>
<span class="node-load">CPU: 42% | RAM: 68%</span>
</div>
<div class="node-stat-row">
<div>
<span class="node-name">nas-node03.lan</span>
<span style="color: var(--text-muted); font-size: 0.72rem; margin-left: 6px;">TrueNAS (48TB RaidZ2)</span>
</div>
<span class="node-load">CPU: 14% | RAM: 82%</span>
</div>
</div>
</section>
</div>
</div>
<!-- Global Footer Bar -->
<footer class="footer-tile">
<div>River Rooks &bull; University of Alabama College of Engineering</div>
<nav class="footer-links-cluster" aria-label="Global Footer Links">
<a href="blog-lander-cards.html" class="footer-btn">Blog Lander</a>
<span class="pipe">|</span>
<a href="feed.xml" class="footer-btn">RSS</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-btn">Forgejo</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-btn">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="#" title="Fingerprint: 0x4A8E92F1C0B3D4E7" class="footer-btn">PGP</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-btn">Catalog ↗</a>
</nav>
</footer>
</main>
</body>
</html>
File diff suppressed because it is too large Load Diff
+889
View File
@@ -0,0 +1,889 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CTF Write-up: Heap Exploitation in glibc 2.35 | River Rooks</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:ital,wght@0,400;0,500;0,700;1,400&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Tile surface */
--bg-elevated: #1c1f26; /* Hovered surface */
--border-color: #2b303b; /* Crisp 1px border */
--text-primary: #e0e6f0; /* Light off-white prose */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Active accent */
--accent-green: #00e676; /* Success / online */
--accent-amber: #ffb700; /* Warning / highlight */
--accent-red: #ff5252; /* Critical alert */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.7;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px 48px 16px;
}
.tiling-layout {
width: 100%;
max-width: 1160px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Top Global Header Bar (Gateway 3 Standard) */
.global-bar {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px 18px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
flex-wrap: wrap;
gap: 10px;
}
.brand-group {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
font-weight: 700;
}
.status-indicator {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
}
.bar-nav {
display: flex;
gap: 14px;
align-items: center;
}
.bar-link {
color: var(--text-muted);
text-decoration: none;
transition: none;
}
.bar-link:hover {
color: var(--accent-cyan);
}
/* Article Workspace: 2-Column Split (Sidebar on Left) */
.article-workspace-grid {
display: grid;
grid-template-columns: 290px 1fr;
gap: 16px;
align-items: start;
}
@media (max-width: 960px) {
.article-workspace-grid {
grid-template-columns: 1fr;
}
}
/* Left Sidebar */
.article-sidebar {
display: flex;
flex-direction: column;
gap: 16px;
position: sticky;
top: 16px;
}
.sidebar-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 18px;
display: flex;
flex-direction: column;
gap: 12px;
}
.sidebar-heading {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.82rem;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.toc-nav {
display: flex;
flex-direction: column;
gap: 4px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
}
.toc-link {
color: var(--text-muted);
text-decoration: none;
padding: 4px 6px;
border-radius: 2px;
transition: none;
line-height: 1.4;
display: block;
}
.toc-link:hover {
background-color: var(--bg-elevated);
color: var(--accent-cyan);
}
.toc-link.active {
color: var(--accent-cyan);
background-color: var(--bg-canvas);
border-left: 2px solid var(--accent-cyan);
font-weight: 700;
}
.toc-sub {
padding-left: 14px;
color: var(--text-muted);
font-size: 0.74rem;
}
/* Collapsible Menu */
.collapsible-drawer {
border: 1px solid var(--border-color);
border-radius: 3px;
background-color: var(--bg-canvas);
overflow: hidden;
}
.collapsible-summary {
background-color: var(--bg-elevated);
padding: 8px 12px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
font-weight: 700;
color: var(--text-primary);
cursor: pointer;
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
}
.collapsible-summary::-webkit-details-marker {
display: none;
}
.collapsible-summary::after {
content: "[+]";
color: var(--accent-cyan);
font-weight: 700;
}
.collapsible-drawer[open] .collapsible-summary::after {
content: "[-]";
}
.drawer-content {
padding: 10px;
display: flex;
flex-direction: column;
gap: 8px;
}
.related-card {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 2px;
padding: 8px 10px;
text-decoration: none;
display: flex;
flex-direction: column;
gap: 3px;
transition: none;
}
.related-card:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.related-date {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.7rem;
color: var(--accent-cyan);
}
.related-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
font-weight: 700;
color: var(--text-primary);
line-height: 1.35;
}
.related-card:hover .related-title {
color: var(--accent-cyan);
}
/* Main Article Tile */
.article-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 36px 40px;
display: flex;
flex-direction: column;
gap: 24px;
}
@media (max-width: 600px) {
.article-tile {
padding: 24px 18px;
}
}
.article-header-block {
border-bottom: 1px solid var(--border-color);
padding-bottom: 24px;
display: flex;
flex-direction: column;
gap: 12px;
}
.article-breadcrumbs {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
display: flex;
align-items: center;
gap: 8px;
}
.article-main-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 2.3rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
line-height: 1.25;
}
@media (max-width: 768px) {
.article-main-title {
font-size: 1.8rem;
}
}
.article-meta-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 14px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--text-muted);
}
.tag-chip {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
padding: 2px 6px;
border-radius: 2px;
color: var(--text-muted);
}
/* Markdown Body */
.markdown-prose {
max-width: 68ch;
font-size: 1.02rem;
color: var(--text-primary);
line-height: 1.7;
}
.markdown-prose p {
margin-bottom: 18px;
}
.markdown-prose h2 {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.45rem;
font-weight: 700;
color: var(--text-primary);
margin: 36px 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid var(--border-color);
}
.markdown-prose h3 {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.15rem;
font-weight: 700;
color: var(--text-primary);
margin: 28px 0 12px 0;
}
.markdown-prose code:not(pre code) {
font-family: 'JetBrains Mono', monospace;
font-size: 0.88rem;
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
padding: 2px 6px;
border-radius: 3px;
color: var(--accent-cyan);
}
/* Mathematical / Algorithm Formula Box */
.formula-card {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-left: 3px solid var(--accent-cyan);
border-radius: 3px;
padding: 14px 18px;
margin: 20px 0;
font-family: 'JetBrains Mono', monospace;
font-size: 0.88rem;
line-height: 1.6;
}
.formula-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
color: var(--text-muted);
text-transform: uppercase;
margin-bottom: 6px;
}
/* Code Block Containers */
.code-block-container {
margin: 22px 0;
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--bg-canvas);
overflow: hidden;
}
.code-block-header {
background-color: var(--bg-surface);
border-bottom: 1px solid var(--border-color);
padding: 6px 14px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
}
.copy-btn {
background: transparent;
border: 1px solid var(--border-color);
color: var(--text-muted);
padding: 2px 8px;
font-family: inherit;
font-size: 0.72rem;
cursor: pointer;
border-radius: 2px;
transition: none;
}
.copy-btn:hover {
border-color: var(--accent-cyan);
color: var(--accent-cyan);
}
.code-block-container pre {
padding: 14px 16px;
overflow-x: auto;
font-family: 'JetBrains Mono', monospace;
font-size: 0.82rem;
line-height: 1.5;
color: var(--text-primary);
}
/* GitHub-style Alerts */
.markdown-callout {
margin: 20px 0;
padding: 14px 18px;
border-radius: 3px;
border: 1px solid var(--border-color);
background-color: var(--bg-canvas);
font-size: 0.94rem;
line-height: 1.6;
}
.callout-caution {
border-left: 4px solid var(--accent-red);
}
.callout-note {
border-left: 4px solid var(--accent-cyan);
}
.callout-title {
font-family: 'IBM Plex Mono', monospace;
font-weight: 700;
font-size: 0.82rem;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
.callout-caution .callout-title { color: var(--accent-red); }
.callout-note .callout-title { color: var(--accent-cyan); }
/* Tables */
.table-container {
width: 100%;
overflow-x: auto;
margin: 22px 0;
}
.markdown-table {
width: 100%;
border-collapse: collapse;
font-size: 0.84rem;
font-family: 'IBM Plex Mono', monospace;
text-align: left;
}
.markdown-table th {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
padding: 8px 12px;
color: var(--text-muted);
font-weight: 700;
}
.markdown-table td {
border: 1px solid var(--border-color);
padding: 8px 12px;
color: var(--text-primary);
}
/* Footnotes Section */
.footnotes-section {
margin-top: 36px;
padding-top: 20px;
border-top: 1px solid var(--border-color);
font-size: 0.84rem;
color: var(--text-muted);
}
.footnote-item {
margin-bottom: 8px;
}
/* Global Footer Tile (Gateway 3 Exact Match) */
.footer-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 10px;
}
.footer-links-cluster {
display: flex;
gap: 12px;
align-items: center;
}
.footer-btn {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-btn:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="tiling-layout">
<!-- Top Global Header Bar (Gateway 3 Standard) -->
<header class="global-bar">
<div class="brand-group">
<span class="status-indicator"></span>
<span>RIVER ROOKS // CTF EXPLOITATION ARCHIVE</span>
</div>
<nav class="bar-nav" aria-label="Navigation">
<a href="service-gateway-tiling.html" class="bar-link">Gateway 3 ↗</a>
<a href="blog-tiling-lead.html" class="bar-link">&larr; Back to Blog Feed</a>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="bar-link">Git Forge</a>
<a href="feed.xml" class="bar-link">RSS</a>
<a href="index.html" class="bar-link">Catalog ↗</a>
</nav>
</header>
<!-- Main Workspace: Sidebar TOC + Reading Canvas -->
<div class="article-workspace-grid">
<!-- Left Sidebar: Document Outline + Collapsible Series -->
<aside class="article-sidebar" aria-label="Table of Contents">
<!-- Outline Tile -->
<div class="sidebar-tile">
<div class="sidebar-heading">
<span>DOCUMENT OUTLINE</span>
<span style="color: var(--accent-cyan);">INDEX</span>
</div>
<nav class="toc-nav" aria-label="Table of Contents">
<a href="#challenge-intro" class="toc-link active">&gt; 1.0 Challenge Synopsis</a>
<a href="#safe-linking" class="toc-link">&gt; 2.0 Safe-Linking Pointer Mangling</a>
<a href="#mangling-math" class="toc-link toc-sub">2.1 Mathematical Derivation</a>
<a href="#gdb-session" class="toc-link">&gt; 3.0 GDB Memory Inspection</a>
<a href="#house-of-apple" class="toc-link">&gt; 4.0 House of Apple Hijacking</a>
<a href="#mitigation-matrix" class="toc-link">&gt; 5.0 Mitigation History</a>
<a href="#footnotes" class="toc-link">&gt; 6.0 References &amp; Notes</a>
</nav>
</div>
<!-- Collapsible Related Dispatches Tile -->
<div class="sidebar-tile">
<div class="sidebar-heading">
<span>SECURITY REPOSITORY</span>
<span>DRAWER</span>
</div>
<details class="collapsible-drawer" open>
<summary class="collapsible-summary">
<span>Security Dispatches (3)</span>
</summary>
<div class="drawer-content">
<a href="article-tiling-firmware.html" class="related-card">
<span class="related-date">2026-07-20 &bull; 8 min</span>
<span class="related-title">Reverse Engineering IoT Firmware: SPI Flash</span>
<span style="color: var(--text-muted); font-size: 0.7rem;">[#firmware] [#hardware]</span>
</a>
<a href="article-tiling-traefik.html" class="related-card">
<span class="related-date">2026-07-15 &bull; 12 min</span>
<span class="related-title">High-Availability Traefik Router with Docker</span>
<span style="color: var(--text-muted); font-size: 0.7rem;">[#homelab] [#networking]</span>
</a>
<a href="blog-tiling-lead.html" class="related-card">
<span class="related-date">2026-07-10 &bull; 6 min</span>
<span class="related-title">POSIX sh vs. Bash: Portability in Modern Scripts</span>
<span style="color: var(--text-muted); font-size: 0.7rem;">[#posix-sh] [#unix]</span>
</a>
</div>
</details>
</div>
<!-- Target Binary Info Tile -->
<div class="sidebar-tile" style="font-family: 'IBM Plex Mono', monospace; font-size: 0.76rem; color: var(--text-muted);">
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
<span>ALLOCATOR:</span>
<span style="color: var(--accent-cyan); font-weight: 700;">GLIBC 2.35-0UBUNTU3</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
<span>EXPLOIT PRIMITIVE:</span>
<span style="color: var(--accent-amber); font-weight: 700;">TCACHE POISONING</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span>TARGET HOOK:</span>
<span style="color: var(--accent-green);">_IO_FILE VTABLE</span>
</div>
</div>
</aside>
<!-- Center Article Tile -->
<article class="article-tile">
<!-- Article Header Block -->
<header class="article-header-block">
<div class="article-breadcrumbs">
<span>~/notes/</span>
<span>&gt;</span>
<span style="color: var(--accent-cyan);">ctf-heap-glibc-2-35.md</span>
</div>
<h1 class="article-main-title">CTF Write-up: Heap Exploitation in glibc 2.35</h1>
<div class="article-meta-row">
<div><span style="color: var(--accent-cyan);">2026-06-28</span></div>
<span>&bull;</span>
<div><span>15 MIN READ</span></div>
<span>&bull;</span>
<div><span>AUTHOR: RIVER ROOKS</span></div>
<span>&bull;</span>
<div>
<span class="tag-chip">[#ctf]</span>
<span class="tag-chip">[#binary-exploitation]</span>
<span class="tag-chip">[#security]</span>
<span class="tag-chip">[#glibc]</span>
</div>
</div>
</header>
<!-- Markdown Prose Content -->
<div class="markdown-prose">
<h2 id="challenge-intro">1.0 Challenge Synopsis</h2>
<p>
During the DEFCON qualifier challenges, our team encountered a hardened 64-bit Linux binary linked against <code>glibc 2.35</code>. Standard legacy heap exploitation techniques—such as poisoning <code>__malloc_hook</code> or <code>__free_hook</code>—are rendered obsolete because upstream glibc maintainers removed all allocation hook pointer symbols entirely in version 2.34.
</p>
<p>
This write-up covers breaking modern <strong>tcache safe-linking pointer mangling</strong> and redirecting execution control flow using the <strong>House of Apple 2</strong> technique against the wide character vtable pointers inside <code>_IO_FILE</code> structures.
</p>
<h2 id="safe-linking">2.0 Safe-Linking Pointer Mangling</h2>
<p>
Introduced in glibc 2.32, safe-linking protects single-linked singly linked list pointers (such as entries inside the Thread Local Cache <code>tcache</code> and fastbins) against off-by-one overwrites and UAF corruption.
</p>
<h3 id="mangling-math">2.1 Mathematical Derivation</h3>
<!-- Formula Callout Box Showcase -->
<div class="formula-card">
<div class="formula-title">POINTER MANGLING FORMULA</div>
<div>Mangled_Pointer (P') = (Address_of_Chunk &gt;&gt; 12) ^ Target_Pointer (P)</div>
</div>
<p>
Since the lowest 12 bits of <code>Address_of_Chunk &gt;&gt; 12</code> are randomized by ASLR, overwriting <code>P'</code> directly without an address leak causes the kernel to dereference invalid virtual memory pages, aborting with <code>SIGSEGV</code>.
</p>
<p>
However, when an arbitrary read primitive leaks the mangled value of the terminal chunk in the tcache bin (where <code>Target_Pointer = NULL</code>), the equation simplifies:
</p>
<div class="formula-card">
<div>Leaked_Mangled = (Chunk_Addr &gt;&gt; 12) ^ 0x0 ==&gt; ASLR_Key = Leaked_Mangled</div>
</div>
<!-- Alert Callout Showcase -->
<div class="markdown-callout callout-caution">
<div class="callout-title">[!CAUTION] Pointer Alignment Constraints</div>
<p>
In glibc 2.35, chunks inserted into tcache bins must also adhere to strict 16-byte address alignment. Forging a fake chunk unaligned to a 16-byte boundary triggers <code>malloc(): unaligned tcache chunk detected</code> aborts.
</p>
</div>
<h2 id="gdb-session">3.0 GDB Memory Inspection</h2>
<p>
Attaching GDB with the <code>pwndbg</code> extension confirms the tcache bin layout and reveals the mangled pointer representation in memory:
</p>
<!-- Code Block Showcase -->
<div class="code-block-container">
<div class="code-block-header">
<div class="code-title">
<span>gdb-pwndbg stdout: heap inspection</span>
<span>GDB</span>
</div>
<button class="copy-btn" onclick="copySnippet(this)">Copy GDB</button>
</div>
<pre><code>pwndbg> tcachebins
tcachebins
0x40 [ 2]: 0x55555555a2a0 &rarr; 0x55555555a260
pwndbg> x/4gx 0x55555555a2a0
0x55555555a2a0: 0x0000555555550730 0x0000000000000000
0x55555555a2b0: 0x0000000000000000 0x0000000000000041
pwndbg> tele 0x55555555a2a0
00:0000 &boxv; 0x55555555a2a0 &rarr; 0x555555550730 (MANGLED POINTER: 0x55555555a260 ^ (0x55555555a >> 12))</code></pre>
</div>
<h2 id="house-of-apple">4.0 House of Apple Hijacking</h2>
<p>
With the ability to allocate chunks onto arbitrary memory addresses via tcache poisoning, we target <code>_IO_list_all</code>. When <code>exit()</code> or <code>abort()</code> is invoked by the runtime, the glibc standard I/O cleanup routine iterates through the <code>_IO_FILE</code> linked list and invokes <code>_IO_wfile_overflow()</code>:
</p>
<!-- C Exploit Snippet Showcase -->
<div class="code-block-container">
<div class="code-block-header">
<div class="code-title">
<span>house_of_apple_payload.c</span>
<span>C99</span>
</div>
<button class="copy-btn" onclick="copySnippet(this)">Copy C Code</button>
</div>
<pre><code>// Constructing fake _IO_FILE_plus structure
struct _IO_FILE_plus *fake_file = (struct _IO_FILE_plus *)payload;
fake_file->file._flags = 0x6873; // "sh" in ASCII
fake_file->file._mode = 1; // Force wide character stream mode
fake_file->vtable = (const struct _IO_jump_t *)wide_vtable_addr;
// Trigger call to system("/bin/sh") on stream flush
fake_file->file._wide_data->_wide_vtable->doallocate = (void *)system_addr;</code></pre>
</div>
<h2 id="mitigation-matrix">5.0 Glibc Heap Mitigation History</h2>
<p>
Tracking defensive hardening milestones across recent glibc releases:
</p>
<!-- Table Showcase -->
<div class="table-container">
<table class="markdown-table">
<thead>
<tr>
<th>glibc Version</th>
<th>Defensive Mitigation Added</th>
<th>Status</th>
<th>Impact on Attackers</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>2.26</code></td>
<td>Thread Cache (tcache) introduced</td>
<td>Active</td>
<td>Initially lacked double-free and count sanity checks.</td>
</tr>
<tr>
<td><code>2.29</code></td>
<td>tcache double-free check (key field)</td>
<td>Active</td>
<td>Stores pointer to tcache struct in chunk data.</td>
</tr>
<tr>
<td><code>2.32</code></td>
<td>Safe-Linking Pointer Mangling</td>
<td>Hardened</td>
<td>Requires ASLR heap base leak to forge pointers.</td>
</tr>
<tr>
<td><code>2.34</code></td>
<td>Removal of <code>__malloc_hook</code> / <code>__free_hook</code></td>
<td>Enforced</td>
<td>Forces targeting of <code>_IO_FILE</code> and stack frames.</td>
</tr>
<tr>
<td><code>2.35</code></td>
<td>Alignment verification on tcache chunks</td>
<td>Enforced</td>
<td>Eliminates misaligned chunk allocations entirely.</td>
</tr>
</tbody>
</table>
</div>
<!-- Footnotes Section Showcase -->
<div class="footnotes-section" id="footnotes">
<div class="footnote-item">
<sup>[1]</sup> Safe-Linking was first proposed by Eyal Itkin for memory allocators in modern operating systems.
</div>
<div class="footnote-item">
<sup>[2]</sup> House of Apple exploitation methodology authored by roderickchan, targeting wide character I/O functions.
</div>
</div>
</div>
</article>
</div>
<!-- Global Footer Tile (Gateway 3 Exact Match) -->
<footer class="footer-tile">
<div>River Rooks &bull; University of Alabama College of Engineering</div>
<nav class="footer-links-cluster" aria-label="Global Footer Links">
<a href="service-gateway-tiling.html" class="footer-btn">Gateway Lander</a>
<span class="pipe">|</span>
<a href="feed.xml" class="footer-btn">RSS</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-btn">Forgejo</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-btn">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="#" title="Fingerprint: 0x4A8E92F1C0B3D4E7" class="footer-btn">PGP</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-btn">Catalog ↗</a>
</nav>
</footer>
</main>
<script>
function copySnippet(btn) {
const code = btn.closest('.code-block-container').querySelector('pre').innerText;
navigator.clipboard.writeText(code).then(() => {
const orig = btn.innerText;
btn.innerText = 'Copied!';
btn.style.color = 'var(--accent-green)';
btn.style.borderColor = 'var(--accent-green)';
setTimeout(() => {
btn.innerText = orig;
btn.style.color = '';
btn.style.borderColor = '';
}, 1800);
});
}
// ScrollSpy for TOC
window.addEventListener('DOMContentLoaded', () => {
const links = document.querySelectorAll('.toc-link');
const sections = [];
links.forEach(link => {
const targetId = link.getAttribute('href').substring(1);
const section = document.getElementById(targetId);
if (section) sections.push({ link, section });
});
window.addEventListener('scroll', () => {
const scrollPos = window.scrollY + 100;
for (let i = sections.length - 1; i >= 0; i--) {
if (sections[i].section.offsetTop <= scrollPos) {
links.forEach(l => l.classList.remove('active'));
sections[i].link.classList.add('active');
break;
}
}
});
});
</script>
</body>
</html>
+887
View File
@@ -0,0 +1,887 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Building a High-Availability Traefik Router with Docker Compose | River Rooks</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:ital,wght@0,400;0,500;0,700;1,400&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Tile surface */
--bg-elevated: #1c1f26; /* Hovered surface */
--border-color: #2b303b; /* Crisp 1px border */
--text-primary: #e0e6f0; /* Light off-white prose */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Active accent */
--accent-green: #00e676; /* Success / online */
--accent-amber: #ffb700; /* Warning / highlight */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.7;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px 48px 16px;
}
.tiling-layout {
width: 100%;
max-width: 1160px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Top Global Header Bar (Gateway 3 Standard) */
.global-bar {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px 18px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
flex-wrap: wrap;
gap: 10px;
}
.brand-group {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
font-weight: 700;
}
.status-indicator {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
}
.bar-nav {
display: flex;
gap: 14px;
align-items: center;
}
.bar-link {
color: var(--text-muted);
text-decoration: none;
transition: none; /* Instant snap */
}
.bar-link:hover {
color: var(--accent-cyan);
}
/* Right Sidebar Reading Workspace Grid: Content (Left) + Sidebar (Right) */
.article-workspace-grid {
display: grid;
grid-template-columns: 1fr 290px;
gap: 16px;
align-items: start;
}
@media (max-width: 960px) {
.article-workspace-grid {
grid-template-columns: 1fr;
}
.article-sidebar {
order: -1; /* Display TOC on top on mobile viewports */
}
}
/* Article Content Container Tile */
.article-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 36px 40px;
display: flex;
flex-direction: column;
gap: 24px;
}
@media (max-width: 600px) {
.article-tile {
padding: 24px 18px;
}
}
.article-header-block {
border-bottom: 1px solid var(--border-color);
padding-bottom: 24px;
display: flex;
flex-direction: column;
gap: 12px;
}
.article-breadcrumbs {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
display: flex;
align-items: center;
gap: 8px;
}
.article-main-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 2.3rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
line-height: 1.25;
}
@media (max-width: 768px) {
.article-main-title {
font-size: 1.8rem;
}
}
.article-meta-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 14px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--text-muted);
}
.tag-chip {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
padding: 2px 6px;
border-radius: 2px;
color: var(--text-muted);
}
/* Right Sidebar Components */
.article-sidebar {
display: flex;
flex-direction: column;
gap: 16px;
position: sticky;
top: 16px;
}
.sidebar-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 18px;
display: flex;
flex-direction: column;
gap: 12px;
}
.sidebar-heading {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.82rem;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
display: flex;
justify-content: space-between;
align-items: center;
}
.toc-nav {
display: flex;
flex-direction: column;
gap: 4px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
}
.toc-link {
color: var(--text-muted);
text-decoration: none;
padding: 4px 6px;
border-radius: 2px;
transition: none;
line-height: 1.4;
display: block;
}
.toc-link:hover {
background-color: var(--bg-elevated);
color: var(--accent-cyan);
}
.toc-link.active {
color: var(--accent-cyan);
background-color: var(--bg-canvas);
border-left: 2px solid var(--accent-cyan);
font-weight: 700;
}
.toc-sub {
padding-left: 14px;
color: var(--text-muted);
font-size: 0.74rem;
}
/* Collapsible Menu */
.collapsible-drawer {
border: 1px solid var(--border-color);
border-radius: 3px;
background-color: var(--bg-canvas);
overflow: hidden;
}
.collapsible-summary {
background-color: var(--bg-elevated);
padding: 8px 12px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
font-weight: 700;
color: var(--text-primary);
cursor: pointer;
list-style: none;
display: flex;
justify-content: space-between;
align-items: center;
user-select: none;
}
.collapsible-summary::-webkit-details-marker {
display: none;
}
.collapsible-summary::after {
content: "[+]";
color: var(--accent-cyan);
font-weight: 700;
}
.collapsible-drawer[open] .collapsible-summary::after {
content: "[-]";
}
.drawer-content {
padding: 10px;
display: flex;
flex-direction: column;
gap: 8px;
}
.related-card {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 2px;
padding: 8px 10px;
text-decoration: none;
display: flex;
flex-direction: column;
gap: 3px;
transition: none;
}
.related-card:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.related-date {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.7rem;
color: var(--accent-cyan);
}
.related-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
font-weight: 700;
color: var(--text-primary);
line-height: 1.35;
}
.related-card:hover .related-title {
color: var(--accent-cyan);
}
/* Markdown Prose Elements */
.markdown-prose {
max-width: 68ch;
font-size: 1.02rem;
color: var(--text-primary);
line-height: 1.7;
}
.markdown-prose p {
margin-bottom: 18px;
}
.markdown-prose h2 {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.45rem;
font-weight: 700;
color: var(--text-primary);
margin: 36px 0 16px 0;
padding-bottom: 8px;
border-bottom: 1px solid var(--border-color);
}
.markdown-prose h3 {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.15rem;
font-weight: 700;
color: var(--text-primary);
margin: 28px 0 12px 0;
}
.markdown-prose code:not(pre code) {
font-family: 'JetBrains Mono', monospace;
font-size: 0.88rem;
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
padding: 2px 6px;
border-radius: 3px;
color: var(--accent-cyan);
}
/* ASCII Network Diagram Component */
.ascii-diagram-container {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 16px;
margin: 22px 0;
overflow-x: auto;
font-family: 'JetBrains Mono', monospace;
font-size: 0.78rem;
color: var(--accent-cyan);
line-height: 1.35;
}
/* Code Block Component */
.code-block-container {
margin: 22px 0;
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--bg-canvas);
overflow: hidden;
}
.code-block-header {
background-color: var(--bg-surface);
border-bottom: 1px solid var(--border-color);
padding: 6px 14px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
}
.copy-btn {
background: transparent;
border: 1px solid var(--border-color);
color: var(--text-muted);
padding: 2px 8px;
font-family: inherit;
font-size: 0.72rem;
cursor: pointer;
border-radius: 2px;
transition: none;
}
.copy-btn:hover {
border-color: var(--accent-cyan);
color: var(--accent-cyan);
}
.code-block-container pre {
padding: 14px 16px;
overflow-x: auto;
font-family: 'JetBrains Mono', monospace;
font-size: 0.82rem;
line-height: 1.5;
color: var(--text-primary);
}
/* Callout Boxes */
.markdown-callout {
margin: 20px 0;
padding: 14px 18px;
border-radius: 3px;
border: 1px solid var(--border-color);
background-color: var(--bg-canvas);
font-size: 0.94rem;
line-height: 1.6;
}
.callout-note {
border-left: 4px solid var(--accent-cyan);
}
.callout-tip {
border-left: 4px solid var(--accent-green);
}
.callout-title {
font-family: 'IBM Plex Mono', monospace;
font-weight: 700;
font-size: 0.82rem;
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
.callout-note .callout-title { color: var(--accent-cyan); }
.callout-tip .callout-title { color: var(--accent-green); }
/* Tables */
.table-container {
width: 100%;
overflow-x: auto;
margin: 22px 0;
}
.markdown-table {
width: 100%;
border-collapse: collapse;
font-size: 0.84rem;
font-family: 'IBM Plex Mono', monospace;
text-align: left;
}
.markdown-table th {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
padding: 8px 12px;
color: var(--text-muted);
font-weight: 700;
}
.markdown-table td {
border: 1px solid var(--border-color);
padding: 8px 12px;
color: var(--text-primary);
}
/* Global Footer Tile (Gateway 3 Exact Match) */
.footer-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 10px;
}
.footer-links-cluster {
display: flex;
gap: 12px;
align-items: center;
}
.footer-btn {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-btn:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="tiling-layout">
<!-- Top Global Header Bar (Gateway 3 Standard) -->
<header class="global-bar">
<div class="brand-group">
<span class="status-indicator"></span>
<span>RIVER ROOKS // INFRASTRUCTURE ARCHIVE</span>
</div>
<nav class="bar-nav" aria-label="Navigation">
<a href="service-gateway-tiling.html" class="bar-link">Gateway 3 ↗</a>
<a href="blog-tiling-lead.html" class="bar-link">&larr; Back to Blog Feed</a>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="bar-link">Git Forge</a>
<a href="feed.xml" class="bar-link">RSS</a>
<a href="index.html" class="bar-link">Catalog ↗</a>
</nav>
</header>
<!-- Right Sidebar Workspace Grid -->
<div class="article-workspace-grid">
<!-- Center / Left: Article Content -->
<article class="article-tile">
<!-- Article Header Block -->
<header class="article-header-block">
<div class="article-breadcrumbs">
<span>~/notes/</span>
<span>&gt;</span>
<span style="color: var(--accent-cyan);">ha-traefik-docker.md</span>
</div>
<h1 class="article-main-title">Building a High-Availability Traefik Router with Docker Compose</h1>
<div class="article-meta-row">
<div><span style="color: var(--accent-cyan);">2026-07-15</span></div>
<span>&bull;</span>
<div><span>12 MIN READ</span></div>
<span>&bull;</span>
<div><span>AUTHOR: RIVER ROOKS</span></div>
<span>&bull;</span>
<div>
<span class="tag-chip">[#homelab]</span>
<span class="tag-chip">[#docker-traefik]</span>
<span class="tag-chip">[#networking]</span>
</div>
</div>
</header>
<!-- Markdown Prose Body -->
<div class="markdown-prose">
<h2 id="architecture">1.0 Ingress Architecture &amp; Topologies</h2>
<p>
When running multi-tier web applications across self-hosted bare-metal clusters, an edge reverse proxy is indispensable. Managing static Nginx reverse proxy virtual host configs by hand becomes unmaintainable once container fleets scale past twenty services.
</p>
<p>
Traefik v3.1 solves this by inspecting the Docker socket daemon directly. When a new container is deployed, Traefik dynamically binds routing rules, attaches middlewares, and provisions automated wildcard TLS certificates without restarting the daemon.
</p>
<!-- ASCII Architecture Diagram Showcase -->
<div class="ascii-diagram-container">
<pre>+-------------------------------------------------------------------------+
| [INTERNET / WAN INGRESS] ==> Cloudflare DNS (DNS-01 ACME Challenge) |
+-------------------------------------------------------------------------+
||
\/
+-------------------------------------------------------------------------+
| gw-node01.lan (OPNsense 24.7 Gateway / NAT Port 443 Forwarding) |
+-------------------------------------------------------------------------+
||
\/ 10GbE VLAN 20 (Server DMZ)
+-------------------------------------------------------------------------+
| pve-node02.lan :: Traefik v3.1 Reverse Proxy Container (:443/:80) |
+-------------------------------------------------------------------------+
|| || ||
\/ (Docker Internal) \/ (Docker Internal) \/ (WireGuard)
+--------------------+ +--------------------+ +--------------------+
| Forgejo Git Server | | CyberChef Security | | Uptime Kuma Status |
+--------------------+ +--------------------+ +--------------------+
</pre>
</div>
<h2 id="dns-challenge">2.0 Automated DNS-01 ACME with Cloudflare</h2>
<p>
Standard HTTP-01 challenges require opening port <code>80/tcp</code> publicly to the internet, exposing unauthenticated ingress surfaces. By employing the <strong>DNS-01 challenge</strong>, Traefik proves domain ownership by creating temporary <code>_acme-challenge.rooks.dev</code> TXT records through the Cloudflare API.
</p>
<!-- Callout Showcase -->
<div class="markdown-callout callout-tip">
<div class="callout-title">[!TIP] Principle of Least Privilege</div>
<p>
Do not use your Global Cloudflare API Key. Instead, generate a scoped API Token with permissions restricted strictly to: <code>Zone.Zone:Read</code> and <code>Zone.DNS:Edit</code> scoped exclusively to your primary apex zone.
</p>
</div>
<h2 id="compose-spec">3.0 Complete Docker Compose Specification</h2>
<p>
Below is the production-tested <code>compose.yaml</code> definition. It mounts the Docker socket in read-only mode (<code>:ro</code>) and stores ACME certificate state inside an encrypted <code>acme.json</code> file with strict <code>0600</code> permissions:
</p>
<!-- Code Block Showcase -->
<div class="code-block-container">
<div class="code-block-header">
<div class="code-title">
<span>docker-compose.traefik.yml</span>
<span>YAML</span>
</div>
<button class="copy-btn" onclick="copySnippet(this)">Copy Compose</button>
</div>
<pre><code>services:
traefik:
image: traefik:v3.1.2
container_name: edge-traefik-proxy
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- proxy-tier
ports:
- "80:80"
- "443:443"
environment:
- CF_DNS_API_TOKEN_FILE=/run/secrets/cf_token
secrets:
- cf_token
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/traefik.yml:/etc/traefik/traefik.yml:ro
- ./data/acme.json:/acme.json
labels:
- "traefik.enable=true"
- "traefik.http.routers.api.rule=Host(`proxy.rooks.dev`)"
- "traefik.http.routers.api.entrypoints=websecure"
- "traefik.http.routers.api.tls.certresolver=cloudflare"
- "traefik.http.routers.api.service=api@internal"
secrets:
cf_token:
file: ./secrets/cloudflare_api_token.txt
networks:
proxy-tier:
external: true</code></pre>
</div>
<h2 id="configuration-matrix">4.0 Configuration Parameters Matrix</h2>
<p>
Key parameters governing the Traefik core engine:
</p>
<!-- Markdown Table Showcase -->
<div class="table-container">
<table class="markdown-table">
<thead>
<tr>
<th>Directive Parameter</th>
<th>Value</th>
<th>Scope</th>
<th>Behavior Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>providers.docker.exposedbydefault</code></td>
<td><code>false</code></td>
<td>Core Security</td>
<td>Prevents untagged backend containers from being routed.</td>
</tr>
<tr>
<td><code>entrypoints.web.http.redirections</code></td>
<td><code>to: websecure</code></td>
<td>HTTP &rarr; HTTPS</td>
<td>Enforces automatic HTTP 301 redirects to port 443.</td>
</tr>
<tr>
<td><code>certificatesresolvers.cf.acme.dnschallenge</code></td>
<td><code>cloudflare</code></td>
<td>Let's Encrypt</td>
<td>Uses Cloudflare DNS provider for wildcard propagation.</td>
</tr>
<tr>
<td><code>certificatesresolvers.cf.acme.storage</code></td>
<td><code>/acme.json</code></td>
<td>Persistent Storage</td>
<td>Encrypted JSON database for RSA/ECDSA key pairs.</td>
</tr>
</tbody>
</table>
</div>
<h2 id="verification">5.0 Verification &amp; Failover Testing</h2>
<p>
Verify TLS certificate issuance and HTTP/3 ALPN negotiation directly via <code>curl</code>:
</p>
<div class="code-block-container">
<div class="code-block-header">
<div class="code-title">
<span>terminal stdout: curl -Iv</span>
<span>Bash</span>
</div>
<button class="copy-btn" onclick="copySnippet(this)">Copy Output</button>
</div>
<pre><code>$ curl -Iv https://proxy.rooks.dev
* Server certificate:
* subject: CN=*.rooks.dev
* start date: Jul 15 00:00:00 2026 GMT
* expire date: Oct 13 23:59:59 2026 GMT
* issuer: C=US, O=Let's Encrypt, CN=R3
* SSL certificate verify ok.
&lt; HTTP/2 200 OK
&lt; server: traefik
&lt; strict-transport-security: max-age=63072000; includeSubDomains; preload</code></pre>
</div>
<p>
Zero downtime updates are achieved through rolling container health checks configured inside Traefik dynamic labels.
</p>
</div>
</article>
<!-- Right Sidebar: TOC & Collapsible Related Articles -->
<aside class="article-sidebar" aria-label="Table of Contents and Series">
<!-- Document Outline Tile -->
<div class="sidebar-tile">
<div class="sidebar-heading">
<span>DOCUMENT OUTLINE</span>
<span style="color: var(--accent-cyan);">INDEX</span>
</div>
<nav class="toc-nav" aria-label="Table of Contents">
<a href="#architecture" class="toc-link active">&gt; 1.0 Ingress Architecture</a>
<a href="#dns-challenge" class="toc-link">&gt; 2.0 DNS-01 ACME Setup</a>
<a href="#compose-spec" class="toc-link">&gt; 3.0 Compose Specification</a>
<a href="#configuration-matrix" class="toc-link">&gt; 4.0 Configuration Matrix</a>
<a href="#verification" class="toc-link">&gt; 5.0 Verification &amp; Testing</a>
</nav>
</div>
<!-- Collapsible Related Dispatches Tile -->
<div class="sidebar-tile">
<div class="sidebar-heading">
<span>RELATED DISPATCHES</span>
<span>DRAWER</span>
</div>
<details class="collapsible-drawer" open>
<summary class="collapsible-summary">
<span>Homelab Series (3)</span>
</summary>
<div class="drawer-content">
<a href="article-tiling-firmware.html" class="related-card">
<span class="related-date">2026-07-20 &bull; 8 min</span>
<span class="related-title">Reverse Engineering IoT Firmware: SPI Flash</span>
<span style="color: var(--text-muted); font-size: 0.7rem;">[#firmware] [#hardware]</span>
</a>
<a href="article-tiling-heap.html" class="related-card">
<span class="related-date">2026-06-28 &bull; 15 min</span>
<span class="related-title">CTF Write-up: Heap Exploitation in glibc 2.35</span>
<span style="color: var(--text-muted); font-size: 0.7rem;">[#ctf] [#security]</span>
</a>
<a href="blog-tiling-lead.html" class="related-card">
<span class="related-date">2026-06-14 &bull; 10 min</span>
<span class="related-title">Zero-Trust Network Isolation with WireGuard</span>
<span style="color: var(--text-muted); font-size: 0.7rem;">[#networking] [#wireguard]</span>
</a>
</div>
</details>
</div>
<!-- Runtime Metadata Tile -->
<div class="sidebar-tile" style="font-family: 'IBM Plex Mono', monospace; font-size: 0.76rem; color: var(--text-muted);">
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
<span>TRAEFIK ENGINE:</span>
<span style="color: var(--accent-cyan); font-weight: 700;">v3.1.2 DOCKER</span>
</div>
<div style="display: flex; justify-content: space-between; margin-bottom: 6px;">
<span>CERT PROVIDER:</span>
<span style="color: var(--accent-green); font-weight: 700;">ACME DNS-01</span>
</div>
<div style="display: flex; justify-content: space-between;">
<span>NODE TARGET:</span>
<span style="color: var(--text-primary);">pve-node02.lan</span>
</div>
</div>
</aside>
</div>
<!-- Global Footer Tile (Gateway 3 Exact Match) -->
<footer class="footer-tile">
<div>River Rooks &bull; University of Alabama College of Engineering</div>
<nav class="footer-links-cluster" aria-label="Global Footer Links">
<a href="service-gateway-tiling.html" class="footer-btn">Gateway Lander</a>
<span class="pipe">|</span>
<a href="feed.xml" class="footer-btn">RSS</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-btn">Forgejo</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-btn">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="#" title="Fingerprint: 0x4A8E92F1C0B3D4E7" class="footer-btn">PGP</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-btn">Catalog ↗</a>
</nav>
</footer>
</main>
<script>
function copySnippet(btn) {
const code = btn.closest('.code-block-container').querySelector('pre').innerText;
navigator.clipboard.writeText(code).then(() => {
const orig = btn.innerText;
btn.innerText = 'Copied!';
btn.style.color = 'var(--accent-green)';
btn.style.borderColor = 'var(--accent-green)';
setTimeout(() => {
btn.innerText = orig;
btn.style.color = '';
btn.style.borderColor = '';
}, 1800);
});
}
// ScrollSpy for Right TOC
window.addEventListener('DOMContentLoaded', () => {
const links = document.querySelectorAll('.toc-link');
const sections = [];
links.forEach(link => {
const targetId = link.getAttribute('href').substring(1);
const section = document.getElementById(targetId);
if (section) sections.push({ link, section });
});
window.addEventListener('scroll', () => {
const scrollPos = window.scrollY + 100;
for (let i = sections.length - 1; i >= 0; i--) {
if (sections[i].section.offsetTop <= scrollPos) {
links.forEach(l => l.classList.remove('active'));
sections[i].link.classList.add('active');
break;
}
}
});
});
</script>
</body>
</html>
+629
View File
@@ -0,0 +1,629 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Field Notes &amp; Dispatches (Cards Lander)</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Card background surface */
--bg-elevated: #1c1f26; /* Hovered card surface */
--border-color: #2b303b; /* Low-contrast 1px border */
--text-primary: #e0e6f0; /* Light off-white prose */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Primary terminal accent */
--accent-green: #00e676; /* Operational status indicator */
--accent-amber: #ffb700; /* Pinned badge accent */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.65;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 0 64px 0;
}
/* Top Sticky Header */
.top-navbar {
width: 100%;
background-color: var(--bg-canvas);
border-bottom: 1px solid var(--border-color);
position: sticky;
top: 0;
z-index: 100;
padding: 12px 24px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.82rem;
}
.nav-brand {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
text-decoration: none;
font-weight: 700;
}
.status-dot {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
}
.nav-links {
display: flex;
gap: 14px;
align-items: center;
}
.nav-item {
color: var(--text-muted);
text-decoration: none;
padding: 3px 6px;
transition: none; /* Instant snap */
}
.nav-item:hover {
color: var(--accent-cyan);
}
.nav-item.active {
color: var(--text-primary);
border-bottom: 1px solid var(--accent-cyan);
}
/* Page Container */
.page-container {
width: 100%;
max-width: 960px;
padding: 40px 20px 0 20px;
}
/* Hero Identity Header */
.hero-header {
margin-bottom: 36px;
padding-bottom: 24px;
border-bottom: 1px solid var(--border-color);
}
.hero-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 2.2rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
margin-bottom: 6px;
}
.hero-subtitle {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.85rem;
color: var(--accent-cyan);
margin-bottom: 14px;
}
.hero-bio {
font-size: 0.98rem;
color: var(--text-primary);
max-width: 70ch;
margin-bottom: 18px;
}
.stack-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
}
.stack-tag {
color: var(--text-muted);
}
/* Featured Lead Card */
.featured-section {
margin-bottom: 44px;
}
.section-label {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.82rem;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 14px;
display: flex;
align-items: center;
gap: 8px;
}
.featured-card {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 24px;
display: flex;
flex-direction: column;
gap: 16px;
position: relative;
}
.featured-card:hover {
border-color: var(--accent-cyan);
}
.featured-badge-row {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
flex-wrap: wrap;
gap: 8px;
}
.badge-pinned {
color: var(--accent-amber);
font-weight: 700;
}
.featured-date {
color: var(--text-muted);
}
.featured-title-link {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.45rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
line-height: 1.3;
transition: none;
}
.featured-title-link:hover {
color: var(--accent-cyan);
}
.featured-title-link:focus {
outline: 2px solid var(--accent-cyan);
}
.featured-abstract {
font-size: 0.95rem;
color: var(--text-primary);
line-height: 1.6;
}
/* Code Snippet in Lead Card */
.code-wrapper {
border: 1px solid var(--border-color);
border-radius: 3px;
background-color: var(--bg-canvas);
overflow: hidden;
font-family: 'JetBrains Mono', monospace;
}
.code-header {
background-color: var(--bg-surface);
border-bottom: 1px solid var(--border-color);
padding: 6px 12px;
font-size: 0.75rem;
color: var(--text-muted);
display: flex;
justify-content: space-between;
}
.code-body {
padding: 12px;
font-size: 0.8rem;
overflow-x: auto;
color: var(--text-primary);
}
.featured-footer {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
padding-top: 8px;
border-top: 1px solid var(--border-color);
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
}
.cta-button {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
padding: 6px 14px;
font-family: 'IBM Plex Mono', monospace;
font-weight: 700;
font-size: 0.82rem;
text-decoration: none;
border-radius: 2px;
border: 1px solid var(--accent-cyan);
transition: none; /* Instant snap */
}
.cta-button:hover {
background-color: transparent;
color: var(--accent-cyan);
}
.cta-button:focus {
outline: 2px solid var(--accent-cyan);
}
/* Recent Dispatches Grid */
.cards-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
margin-bottom: 48px;
}
@media (max-width: 720px) {
.cards-grid {
grid-template-columns: 1fr;
}
}
.dispatch-card {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 12px;
transition: none;
}
.dispatch-card:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.card-meta-row {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.75rem;
color: var(--text-muted);
}
.card-date {
color: var(--accent-cyan);
}
.card-heading-link {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.05rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
line-height: 1.35;
transition: none;
}
.dispatch-card:hover .card-heading-link {
color: var(--accent-cyan);
}
.card-heading-link:focus {
outline: 2px solid var(--accent-cyan);
}
.card-summary {
font-size: 0.88rem;
color: var(--text-primary);
line-height: 1.55;
}
.card-bottom-row {
display: flex;
justify-content: space-between;
align-items: baseline;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
padding-top: 10px;
border-top: 1px solid var(--border-color);
flex-wrap: wrap;
gap: 6px;
}
.card-tag-cluster {
display: flex;
flex-wrap: wrap;
gap: 6px;
color: var(--text-muted);
}
.read-more-link {
color: var(--accent-cyan);
text-decoration: none;
font-weight: 500;
}
.read-more-link:hover {
text-decoration: underline;
}
/* Bottom Site Footer */
.site-footer {
border-top: 1px solid var(--border-color);
padding-top: 24px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--text-muted);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.footer-links {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.footer-link {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-link:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.footer-link:focus {
outline: 2px solid var(--accent-cyan);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<!-- Sticky Top Navigation -->
<header class="top-navbar">
<a href="#" class="nav-brand">
<span class="status-dot"></span>
<span>ROOKS.DEV // NOTES</span>
</a>
<nav class="nav-links" aria-label="Site Navigation">
<a href="#" class="nav-item active">[Dispatches]</a>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="nav-item">Git</a>
<a href="feed.xml" class="nav-item">RSS</a>
<a href="index.html" class="nav-item">Catalog ↗</a>
</nav>
</header>
<main class="page-container">
<!-- Hero Identity Block -->
<section class="hero-header" aria-label="Author Profile">
<h1 class="hero-title">River Rooks</h1>
<div class="hero-subtitle">SYSTEMS ARCHITECTURE &amp; CYBERSECURITY • ALABAMA, USA</div>
<p class="hero-bio">
Cybersecurity student at the University of Alabama College of Engineering. Architecting high-throughput homelab networks, reverse engineering IoT firmware, and writing zero-allocation Unix utilities.
</p>
<div class="stack-tags">
<span class="stack-tag">[#network-forensics]</span>
<span class="stack-tag">[#docker-traefik]</span>
<span class="stack-tag">[#arch-linux]</span>
<span class="stack-tag">[#neovim]</span>
<span class="stack-tag">[#wireguard]</span>
<span class="stack-tag">[#posix-sh]</span>
<span class="stack-tag">[#python]</span>
<span class="stack-tag">[#rust]</span>
</div>
</section>
<!-- Featured Lead Story -->
<section class="featured-section" aria-label="Featured Technical Article">
<div class="section-label">
<span>// Pinned Technical Write-Up</span>
</div>
<article class="featured-card">
<div class="featured-badge-row">
<span class="badge-pinned">[PINNED WRITE-UP]</span>
<span class="featured-date">2026-07-20 • 8 min read</span>
</div>
<h2 class="featured-title">
<a href="#" class="featured-title-link">Reverse Engineering IoT Firmware: SPI Flash Extraction</a>
</h2>
<p class="featured-abstract">
In security audits of embedded systems, retrieving the firmware is the critical first step. When over-the-air updates are encrypted and debug ports (UART, JTAG) are disabled in production silicon, dumping physical SPI Flash memory becomes necessary. This writeup covers extracting firmware from an 8-pin SOP SPI flash chip using a Bus Pirate and flashrom to bypass bootloader lockdowns.
</p>
<!-- Code Snippet -->
<div class="code-wrapper">
<div class="code-header">
<span>extract_firmware.sh</span>
<span>POSIX sh</span>
</div>
<div class="code-body">
<pre><code># Query the SPI flash details using flashrom via Bus Pirate
flashrom -p buspirate_spi:dev=/dev/ttyUSB0,spispeed=1M -r firmware_dump.bin
# Verify the integrity of the binary dump by comparing hashes
if [ $? -eq 0 ]; then
echo "Dump successful. Running md5sum..."
md5sum firmware_dump.bin
fi</code></pre>
</div>
</div>
<div class="featured-footer">
<div class="stack-tags">
<span class="stack-tag">[#firmware]</span>
<span class="stack-tag">[#hardware]</span>
<span class="stack-tag">[#security]</span>
<span class="stack-tag">[#bus-pirate]</span>
</div>
<a href="#" class="cta-button">READ FULL WRITEUP [8 MIN] &rarr;</a>
</div>
</article>
</section>
<!-- Recent Dispatches Grid -->
<section class="recent-section" aria-label="Recent Technical Dispatches">
<div class="section-label">
<span>// Recent Dispatches &amp; Architecture Notes</span>
</div>
<div class="cards-grid">
<!-- Card 1: Traefik -->
<article class="dispatch-card">
<div>
<div class="card-meta-row">
<time datetime="2026-07-15" class="card-date">2026-07-15</time>
<span>12 min read</span>
</div>
<h3 style="margin: 10px 0 8px 0;">
<a href="#" class="card-heading-link">Building a High-Availability Traefik Router with Docker Compose</a>
</h3>
<p class="card-summary">
Architecting automated TLS wildcard certificate propagation, Docker provider label routing, zero-downtime container updates, and backend health-check failover routes.
</p>
</div>
<div class="card-bottom-row">
<div class="card-tag-cluster">
<span>[#homelab]</span>
<span>[#docker-traefik]</span>
</div>
<a href="#" class="read-more-link">Read &rarr;</a>
</div>
</article>
<!-- Card 2: POSIX sh -->
<article class="dispatch-card">
<div>
<div class="card-meta-row">
<time datetime="2026-07-10" class="card-date">2026-07-10</time>
<span>6 min read</span>
</div>
<h3 style="margin: 10px 0 8px 0;">
<a href="#" class="card-heading-link">POSIX sh vs. Bash: Portability in Modern Scripts</a>
</h3>
<p class="card-summary">
Analyzing edge-case incompatibilities, syntax overhead, eliminating bashisms, and writing fast, bulletproof bootstrapping scripts for Alpine and embedded targets conforming to IEEE Std 1003.1.
</p>
</div>
<div class="card-bottom-row">
<div class="card-tag-cluster">
<span>[#posix-sh]</span>
<span>[#unix]</span>
</div>
<a href="#" class="read-more-link">Read &rarr;</a>
</div>
</article>
<!-- Card 3: Heap Exploitation -->
<article class="dispatch-card">
<div>
<div class="card-meta-row">
<time datetime="2026-06-28" class="card-date">2026-06-28</time>
<span>15 min read</span>
</div>
<h3 style="margin: 10px 0 8px 0;">
<a href="#" class="card-heading-link">CTF Write-up: Heap Exploitation in glibc 2.35</a>
</h3>
<p class="card-summary">
Deconstructing modern glibc memory allocator defenses: tcache safe-linking pointer mangling, bypass primitives, and House of Apple file structure hijacking.
</p>
</div>
<div class="card-bottom-row">
<div class="card-tag-cluster">
<span>[#ctf]</span>
<span>[#security]</span>
</div>
<a href="#" class="read-more-link">Read &rarr;</a>
</div>
</article>
<!-- Card 4: WireGuard OPNsense -->
<article class="dispatch-card">
<div>
<div class="card-meta-row">
<time datetime="2026-06-14" class="card-date">2026-06-14</time>
<span>10 min read</span>
</div>
<h3 style="margin: 10px 0 8px 0;">
<a href="#" class="card-heading-link">Zero-Trust Network Isolation with WireGuard &amp; OPNsense VLANs</a>
</h3>
<p class="card-summary">
Designing strict zone-based firewall rules across isolated VLANs (Management, Server DMZ, IoT, Trusted Clients) with point-to-point WireGuard cryptographic routing.
</p>
</div>
<div class="card-bottom-row">
<div class="card-tag-cluster">
<span>[#networking]</span>
<span>[#wireguard]</span>
</div>
<a href="#" class="read-more-link">Read &rarr;</a>
</div>
</article>
</div>
</section>
<!-- Site Footer -->
<footer class="site-footer">
<div>River Rooks &copy; 2026 • Node RACK-01</div>
<nav class="footer-links" aria-label="Footer Links">
<a href="feed.xml" class="footer-link">Feed</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-link">Git Forge</a>
<span class="pipe">|</span>
<a href="https://github.com" target="_blank" rel="noopener" class="footer-link">GitHub</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-link">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="#" title="PGP: 0x4A8E92F1C0B3D4E7" class="footer-link">PGP</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-link">Samples ↗</a>
</nav>
</footer>
</main>
</body>
</html>
+483
View File
@@ -0,0 +1,483 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Technical Notes &amp; Blog (Stream Lander)</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Card surface background */
--bg-elevated: #1c1f26; /* Hovered / elevated state */
--border-color: #2b303b; /* Low-contrast 1px border */
--text-primary: #e0e6f0; /* Off-white prose */
--text-muted: #8a95a5; /* Monospaced metadata gray */
--accent-cyan: #00f0ff; /* Primary terminal accent */
--accent-green: #00e676; /* Operational indicator */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.65;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 48px 20px 64px 20px;
}
/* Reading container strictly capped at 65ch */
.stream-container {
width: 100%;
max-width: 65ch;
}
/* POSIX Path Top Banner */
.top-path-bar {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
margin-bottom: 24px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
}
.path-display {
display: flex;
align-items: center;
gap: 8px;
}
.status-dot {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
flex-shrink: 0;
}
.status-badge {
color: var(--accent-green);
font-weight: 500;
}
/* Identity Section */
.author-name {
font-family: 'IBM Plex Mono', monospace;
font-size: 2rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
margin-bottom: 6px;
}
.author-role {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.85rem;
color: var(--accent-cyan);
letter-spacing: 0.05em;
text-transform: uppercase;
margin-bottom: 16px;
}
.author-bio {
font-size: 0.98rem;
color: var(--text-primary);
margin-bottom: 24px;
}
/* Topic Filter Pills */
.filter-section {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
margin-bottom: 28px;
}
.filter-label {
color: var(--text-muted);
margin-right: 4px;
}
.filter-pill {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
color: var(--text-muted);
padding: 3px 8px;
border-radius: 3px;
cursor: pointer;
transition: none; /* Instant snap */
text-decoration: none;
}
.filter-pill:hover {
border-color: var(--accent-cyan);
color: var(--text-primary);
background-color: var(--bg-elevated);
}
.filter-pill.active {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
border-color: var(--accent-cyan);
font-weight: 700;
}
/* Section Divider */
.section-divider {
border: 0;
border-top: 1px solid var(--border-color);
margin: 28px 0;
}
/* Article Stream Entries */
.articles-stream {
display: flex;
flex-direction: column;
gap: 36px;
}
.article-entry {
display: flex;
flex-direction: column;
gap: 8px;
}
.article-entry.hidden {
display: none;
}
.article-meta {
display: flex;
align-items: center;
gap: 12px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
}
.article-date {
color: var(--accent-cyan);
}
.article-title-link {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.22rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
line-height: 1.35;
display: inline-block;
transition: none; /* Instant snap */
}
.article-title-link::before {
content: '> ';
color: var(--border-color);
transition: none;
}
.article-title-link:hover {
color: var(--accent-cyan);
}
.article-title-link:hover::before {
color: var(--accent-cyan);
}
.article-title-link:focus {
outline: 2px solid var(--accent-cyan);
outline-offset: 2px;
}
.article-abstract {
font-size: 0.94rem;
color: var(--text-primary);
line-height: 1.6;
}
.article-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.75rem;
margin-top: 4px;
}
.article-tag {
color: var(--text-muted);
}
.article-read-cta {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--accent-cyan);
text-decoration: none;
align-self: flex-start;
margin-top: 2px;
transition: none;
}
.article-read-cta:hover {
text-decoration: underline;
}
/* Piped Footer */
.stream-footer {
margin-top: 48px;
padding-top: 24px;
border-top: 1px solid var(--border-color);
font-family: 'IBM Plex Mono', monospace;
font-size: 0.82rem;
color: var(--text-muted);
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.footer-links {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.footer-link {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-link:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.footer-link:focus {
outline: 2px solid var(--accent-cyan);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="stream-container">
<!-- POSIX Path Top Banner -->
<header class="top-path-bar">
<div class="path-display">
<span class="status-dot" title="Cluster state: operational"></span>
<span>~/blog/notes/</span>
</div>
<div class="status-badge">[5 POSTS • FEED ONLINE]</div>
</header>
<!-- Identity & Bio -->
<section class="identity-section" aria-label="Author Identity">
<h1 class="author-name">River Rooks</h1>
<div class="author-role">Systems Architecture &amp; Cybersecurity</div>
<p class="author-bio">
Cybersecurity student at the University of Alabama College of Engineering, focusing on network forensics, self-hosted infrastructure, and bare-metal systems. Field notes, architecture specs, and low-level security research.
</p>
</section>
<!-- Quick Topic Filters -->
<nav class="filter-section" aria-label="Filter Articles by Topic">
<span class="filter-label">Filter:</span>
<button class="filter-pill active" data-filter="all" onclick="filterArticles('all', this)">[All (5)]</button>
<button class="filter-pill" data-filter="security" onclick="filterArticles('security', this)">[#security (3)]</button>
<button class="filter-pill" data-filter="homelab" onclick="filterArticles('homelab', this)">[#homelab (2)]</button>
<button class="filter-pill" data-filter="networking" onclick="filterArticles('networking', this)">[#networking (2)]</button>
<button class="filter-pill" data-filter="unix" onclick="filterArticles('unix', this)">[#unix (1)]</button>
</nav>
<hr class="section-divider">
<!-- Articles Stream (Reverse Chronological) -->
<section class="articles-stream" id="articlesStream" aria-label="Article Feed">
<!-- Post 1: Firmware -->
<article class="article-entry" data-category="security firmware">
<div class="article-meta">
<time datetime="2026-07-20" class="article-date">2026-07-20</time>
<span>•</span>
<span>8 min read</span>
<span>•</span>
<span>~/notes/iot-firmware-spi-extraction.md</span>
</div>
<h2 class="article-title">
<a href="#" class="article-title-link">Reverse Engineering IoT Firmware: SPI Flash Extraction</a>
</h2>
<p class="article-abstract">
In security audits of embedded systems, retrieving the firmware is the critical first step. When over-the-air updates are encrypted and debug ports (UART, JTAG) are disabled in production silicon, dumping physical SPI Flash memory becomes necessary. This writeup covers extracting firmware from an 8-pin SOP SPI flash chip using a Bus Pirate and flashrom to bypass bootloader lockdowns.
</p>
<div class="article-tags">
<span class="article-tag">[#firmware]</span>
<span class="article-tag">[#hardware]</span>
<span class="article-tag">[#security]</span>
<span class="article-tag">[#bus-pirate]</span>
</div>
<a href="#" class="article-read-cta">Read Writeup [8 min] &rarr;</a>
</article>
<!-- Post 2: Traefik Docker -->
<article class="article-entry" data-category="homelab networking">
<div class="article-meta">
<time datetime="2026-07-15" class="article-date">2026-07-15</time>
<span>•</span>
<span>12 min read</span>
<span>•</span>
<span>~/notes/ha-traefik-docker.md</span>
</div>
<h2 class="article-title">
<a href="#" class="article-title-link">Building a High-Availability Traefik Router with Docker Compose</a>
</h2>
<p class="article-abstract">
Architecting automated TLS wildcard certificate propagation, Docker provider label routing, zero-downtime container updates, and backend health-check failover routes on bare-metal infrastructure.
</p>
<div class="article-tags">
<span class="article-tag">[#homelab]</span>
<span class="article-tag">[#docker-traefik]</span>
<span class="article-tag">[#networking]</span>
</div>
<a href="#" class="article-read-cta">Read Writeup [12 min] &rarr;</a>
</article>
<!-- Post 3: POSIX sh -->
<article class="article-entry" data-category="unix">
<div class="article-meta">
<time datetime="2026-07-10" class="article-date">2026-07-10</time>
<span>•</span>
<span>6 min read</span>
<span>•</span>
<span>~/notes/posix-sh-vs-bash.md</span>
</div>
<h2 class="article-title">
<a href="#" class="article-title-link">POSIX sh vs. Bash: Portability in Modern Scripts</a>
</h2>
<p class="article-abstract">
Analyzing edge-case incompatibilities, syntax overhead, eliminating bashisms, and writing fast, bulletproof bootstrapping scripts for Alpine and embedded targets conforming to IEEE Std 1003.1.
</p>
<div class="article-tags">
<span class="article-tag">[#posix-sh]</span>
<span class="article-tag">[#shell]</span>
<span class="article-tag">[#unix]</span>
</div>
<a href="#" class="article-read-cta">Read Writeup [6 min] &rarr;</a>
</article>
<!-- Post 4: Heap Exploitation -->
<article class="article-entry" data-category="security">
<div class="article-meta">
<time datetime="2026-06-28" class="article-date">2026-06-28</time>
<span>•</span>
<span>15 min read</span>
<span>•</span>
<span>~/notes/ctf-heap-glibc-2-35.md</span>
</div>
<h2 class="article-title">
<a href="#" class="article-title-link">CTF Write-up: Heap Exploitation in glibc 2.35</a>
</h2>
<p class="article-abstract">
Deconstructing modern glibc memory allocator defenses: tcache safe-linking pointer mangling, bypass primitives, and House of Apple file structure hijacking for arbitrary code execution.
</p>
<div class="article-tags">
<span class="article-tag">[#ctf]</span>
<span class="article-tag">[#binary-exploitation]</span>
<span class="article-tag">[#security]</span>
</div>
<a href="#" class="article-read-cta">Read Writeup [15 min] &rarr;</a>
</article>
<!-- Post 5: Zero Trust Network -->
<article class="article-entry" data-category="networking security homelab">
<div class="article-meta">
<time datetime="2026-06-14" class="article-date">2026-06-14</time>
<span>•</span>
<span>10 min read</span>
<span>•</span>
<span>~/notes/zero-trust-wireguard-opnsense.md</span>
</div>
<h2 class="article-title">
<a href="#" class="article-title-link">Zero-Trust Network Isolation with WireGuard &amp; OPNsense VLANs</a>
</h2>
<p class="article-abstract">
Designing strict zone-based firewall rules across isolated VLANs (Management, Server DMZ, IoT, Trusted Clients) with point-to-point WireGuard cryptographic routing.
</p>
<div class="article-tags">
<span class="article-tag">[#networking]</span>
<span class="article-tag">[#wireguard]</span>
<span class="article-tag">[#security]</span>
</div>
<a href="#" class="article-read-cta">Read Writeup [10 min] &rarr;</a>
</article>
</section>
<!-- Piped Footer -->
<footer class="stream-footer">
<div>River Rooks &copy; 2026</div>
<nav class="footer-links" aria-label="Footer Links">
<a href="feed.xml" class="footer-link">RSS Feed</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-link">Git Forge</a>
<span class="pipe">|</span>
<a href="https://github.com" target="_blank" rel="noopener" class="footer-link">GitHub</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-link">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="#" title="PGP: 0x4A8E92F1C0B3D4E7" class="footer-link">PGP Key</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-link">Catalog ↗</a>
</nav>
</footer>
</main>
<script>
function filterArticles(category, btn) {
// Toggle active pill
document.querySelectorAll('.filter-pill').forEach(el => el.classList.remove('active'));
btn.classList.add('active');
const entries = document.querySelectorAll('.article-entry');
entries.forEach(entry => {
if (category === 'all') {
entry.classList.remove('hidden');
} else {
const cats = entry.getAttribute('data-category') || '';
if (cats.includes(category)) {
entry.classList.remove('hidden');
} else {
entry.classList.add('hidden');
}
}
});
}
</script>
</body>
</html>
+617
View File
@@ -0,0 +1,617 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Terminal Index Blog Lander</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Table & card surface */
--bg-elevated: #1c1f26; /* Selected row / elevated state */
--border-color: #2b303b; /* 1px solid low-contrast border */
--text-primary: #e0e6f0; /* Light off-white prose */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Primary terminal accent */
--accent-green: #00e676; /* Operational indicator */
--accent-amber: #ffb700; /* Warning / highlight */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'IBM Plex Mono', monospace;
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 36px 16px 48px 16px;
}
.app-wrapper {
width: 100%;
max-width: 960px;
}
/* Terminal Window Frame */
.terminal-window {
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--bg-surface);
overflow: hidden;
}
/* Window Titlebar */
.terminal-titlebar {
background-color: var(--bg-canvas);
border-bottom: 1px solid var(--border-color);
padding: 8px 14px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.78rem;
color: var(--text-muted);
}
.window-controls {
display: flex;
gap: 6px;
align-items: center;
}
.window-dot {
width: 8px;
height: 8px;
background-color: var(--border-color);
display: inline-block;
}
.window-title {
color: var(--text-primary);
font-weight: 700;
}
/* Terminal Body */
.terminal-body {
padding: 20px;
}
/* System Header Info */
.system-header {
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border-color);
}
.system-identity {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: baseline;
gap: 8px;
margin-bottom: 8px;
}
.author-title {
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
}
.author-role {
font-size: 0.8rem;
color: var(--accent-cyan);
}
.system-bio {
font-family: 'Inter', sans-serif;
font-size: 0.9rem;
color: var(--text-muted);
max-width: 75ch;
line-height: 1.5;
}
/* Command Prompt & Live Grep Filter */
.prompt-bar {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 10px 14px;
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.prompt-text {
color: var(--accent-cyan);
font-weight: 700;
font-size: 0.85rem;
white-space: nowrap;
}
.grep-label {
color: var(--text-muted);
font-size: 0.82rem;
}
.grep-input {
background: transparent;
border: none;
outline: none;
color: var(--text-primary);
font-family: 'IBM Plex Mono', monospace;
font-size: 0.85rem;
flex: 1;
min-width: 140px;
}
.grep-input::placeholder {
color: var(--text-muted);
opacity: 0.6;
}
.grep-count {
color: var(--accent-green);
font-size: 0.78rem;
}
/* Tabular Directory View */
.table-responsive {
width: 100%;
overflow-x: auto;
margin-bottom: 20px;
}
.directory-table {
width: 100%;
border-collapse: collapse;
font-size: 0.82rem;
text-align: left;
}
.directory-table th {
padding: 8px 12px;
border-bottom: 1px solid var(--border-color);
color: var(--text-muted);
font-weight: 500;
background-color: var(--bg-canvas);
white-space: nowrap;
}
.directory-table td {
padding: 10px 12px;
border-bottom: 1px solid var(--border-color);
color: var(--text-primary);
vertical-align: middle;
white-space: nowrap;
}
.directory-row {
cursor: pointer;
transition: none; /* Instant snap */
}
.directory-row:hover {
background-color: var(--bg-elevated);
}
.directory-row.active {
background-color: var(--bg-elevated);
border-left: 2px solid var(--accent-cyan);
}
.col-title {
white-space: normal !important;
min-width: 280px;
}
.entry-link {
color: var(--text-primary);
text-decoration: none;
font-weight: 500;
display: inline-block;
transition: none;
}
.directory-row:hover .entry-link,
.directory-row.active .entry-link {
color: var(--accent-cyan);
}
.tag-badge {
color: var(--text-muted);
font-size: 0.75rem;
}
.time-badge {
color: var(--text-muted);
font-size: 0.78rem;
}
.preview-indicator {
color: var(--accent-cyan);
font-size: 0.75rem;
text-decoration: none;
padding: 2px 6px;
border: 1px solid transparent;
}
.directory-row:hover .preview-indicator {
border-color: var(--accent-cyan);
}
/* Live Inspection / Preview Pane */
.preview-pane {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 16px;
margin-top: 12px;
display: flex;
flex-direction: column;
gap: 12px;
}
.preview-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
font-size: 0.78rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 8px;
}
.preview-filename {
color: var(--accent-cyan);
font-weight: 700;
}
.preview-abstract {
font-family: 'Inter', sans-serif;
font-size: 0.92rem;
color: var(--text-primary);
line-height: 1.6;
}
.preview-code-block {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
padding: 12px;
border-radius: 3px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.78rem;
overflow-x: auto;
color: var(--text-primary);
}
.preview-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 6px;
}
.read-btn {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
border: 1px solid var(--accent-cyan);
padding: 4px 10px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
font-weight: 700;
text-decoration: none;
border-radius: 2px;
cursor: pointer;
transition: none;
}
.read-btn:hover {
background-color: transparent;
color: var(--accent-cyan);
}
/* Terminal Status Bar at Bottom */
.terminal-statusbar {
background-color: var(--bg-canvas);
border-top: 1px solid var(--border-color);
padding: 8px 16px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.75rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 8px;
}
.footer-links {
display: flex;
gap: 12px;
align-items: center;
}
.footer-link {
color: var(--text-primary);
text-decoration: none;
padding: 1px 4px;
transition: none;
}
.footer-link:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.footer-link:focus {
outline: 2px solid var(--accent-cyan);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="app-wrapper">
<div class="terminal-window">
<!-- Titlebar -->
<div class="terminal-titlebar">
<div class="window-controls">
<span class="window-dot"></span>
<span class="window-dot"></span>
<span class="window-dot"></span>
<span class="window-title" style="margin-left: 6px;">rooks@sys: ~/articles</span>
</div>
<div>POSIX SH • UTF-8 • [READ-ONLY]</div>
</div>
<div class="terminal-body">
<!-- System Identity Header -->
<header class="system-header">
<div class="system-identity">
<h1 class="author-title">River Rooks // Articles Directory</h1>
<span class="author-role">[SYSTEMS ARCHITECTURE &amp; CYBERSECURITY]</span>
</div>
<p class="system-bio">
Field notes and technical write-ups covering embedded firmware reverse engineering, network forensics, low-level memory exploitation, and self-hosted infrastructure.
</p>
</header>
<!-- Command Prompt with Live Filter -->
<div class="prompt-bar" role="search">
<span class="prompt-text">dev-blog@system:~$</span>
<span class="grep-label">grep -i</span>
<input
type="text"
id="searchInput"
class="grep-input"
placeholder="filter by keyword, tag, or title..."
oninput="handleSearch(this.value)"
aria-label="Filter articles"
>
<span class="grep-count" id="matchCounter">5/5 matches</span>
</div>
<!-- Directory Listing Table -->
<div class="table-responsive">
<table class="directory-table" id="directoryTable" aria-label="Articles directory table">
<thead>
<tr>
<th scope="col">PERMS</th>
<th scope="col">DATE</th>
<th scope="col">PRIMARY TAG</th>
<th scope="col">ARTICLE TITLE / FILE</th>
<th scope="col">EST. TIME</th>
<th scope="col">ACTION</th>
</tr>
</thead>
<tbody id="tableBody">
<tr class="directory-row active" onclick="selectRow(0)" data-index="0" data-keywords="firmware hardware security bus-pirate spi flash extraction iot">
<td><span style="color: var(--text-muted);">-rw-r--r--</span></td>
<td><time datetime="2026-07-20" style="color: var(--accent-cyan);">2026-07-20</time></td>
<td><span class="tag-badge">[#firmware]</span></td>
<td class="col-title">
<a href="#" class="entry-link">&gt; Reverse Engineering IoT Firmware: SPI Flash Extraction</a>
</td>
<td><span class="time-badge">8 mins</span></td>
<td><span class="preview-indicator">[INSPECT]</span></td>
</tr>
<tr class="directory-row" onclick="selectRow(1)" data-index="1" data-keywords="homelab docker traefik networking proxy compose">
<td><span style="color: var(--text-muted);">-rw-r--r--</span></td>
<td><time datetime="2026-07-15" style="color: var(--accent-cyan);">2026-07-15</time></td>
<td><span class="tag-badge">[#homelab]</span></td>
<td class="col-title">
<a href="#" class="entry-link">&gt; Building a High-Availability Traefik Router with Docker Compose</a>
</td>
<td><span class="time-badge">12 mins</span></td>
<td><span class="preview-indicator">[INSPECT]</span></td>
</tr>
<tr class="directory-row" onclick="selectRow(2)" data-index="2" data-keywords="posix-sh shell unix portability scripts bashisms">
<td><span style="color: var(--text-muted);">-rw-r--r--</span></td>
<td><time datetime="2026-07-10" style="color: var(--accent-cyan);">2026-07-10</time></td>
<td><span class="tag-badge">[#posix-sh]</span></td>
<td class="col-title">
<a href="#" class="entry-link">&gt; POSIX sh vs. Bash: Portability in Modern Scripts</a>
</td>
<td><span class="time-badge">6 mins</span></td>
<td><span class="preview-indicator">[INSPECT]</span></td>
</tr>
<tr class="directory-row" onclick="selectRow(3)" data-index="3" data-keywords="ctf binary exploitation heap glibc 2.35 memory security">
<td><span style="color: var(--text-muted);">-rw-r--r--</span></td>
<td><time datetime="2026-06-28" style="color: var(--accent-cyan);">2026-06-28</time></td>
<td><span class="tag-badge">[#ctf]</span></td>
<td class="col-title">
<a href="#" class="entry-link">&gt; CTF Write-up: Heap Exploitation in glibc 2.35</a>
</td>
<td><span class="time-badge">15 mins</span></td>
<td><span class="preview-indicator">[INSPECT]</span></td>
</tr>
<tr class="directory-row" onclick="selectRow(4)" data-index="4" data-keywords="networking wireguard security zerotrust opnsense vlans">
<td><span style="color: var(--text-muted);">-rw-r--r--</span></td>
<td><time datetime="2026-06-14" style="color: var(--accent-cyan);">2026-06-14</time></td>
<td><span class="tag-badge">[#networking]</span></td>
<td class="col-title">
<a href="#" class="entry-link">&gt; Zero-Trust Network Isolation with WireGuard &amp; OPNsense VLANs</a>
</td>
<td><span class="time-badge">10 mins</span></td>
<td><span class="preview-indicator">[INSPECT]</span></td>
</tr>
</tbody>
</table>
</div>
<!-- Live Inspection / Details Panel -->
<section class="preview-pane" id="previewPane" aria-label="Article Details Preview">
<div class="preview-header">
<span class="preview-filename" id="previewFilePath">~/notes/iot-firmware-spi-extraction.md</span>
<span id="previewMeta">2026-07-20 • 8 mins read • [#firmware] [#hardware] [#security]</span>
</div>
<p class="preview-abstract" id="previewAbstract">
In security audits of embedded systems, retrieving the firmware is the critical first step. When over-the-air updates are encrypted and debug ports (UART, JTAG) are disabled in production silicon, dumping physical SPI Flash memory becomes necessary. This writeup covers extracting firmware from an 8-pin SOP SPI flash chip using a Bus Pirate and flashrom to bypass bootloader lockdowns.
</p>
<div class="preview-code-block" id="previewCode">
<pre><code># Query the SPI flash details using flashrom via Bus Pirate
flashrom -p buspirate_spi:dev=/dev/ttyUSB0,spispeed=1M -r firmware_dump.bin
# Verify the integrity of the binary dump by comparing hashes
if [ $? -eq 0 ]; then
echo "Dump successful. Running md5sum..."
md5sum firmware_dump.bin
fi</code></pre>
</div>
<div class="preview-actions">
<span style="font-size: 0.78rem; color: var(--text-muted);">Format: IEEE Std 1003.1 Markdown</span>
<a href="#" class="read-btn" id="previewReadBtn">READ COMPLETE WRITEUP &rarr;</a>
</div>
</section>
</div>
<!-- Bottom Statusbar -->
<footer class="terminal-statusbar">
<div>5 FILES INDEXED • 51m READING DURATION • STATUS: 0 (OK)</div>
<nav class="footer-links" aria-label="Terminal Footer Links">
<a href="feed.xml" class="footer-link">feed.xml</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-link">git.rooks.dev</a>
<span class="pipe">|</span>
<a href="https://github.com" target="_blank" rel="noopener" class="footer-link">github</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-link">catalog ↗</a>
</nav>
</footer>
</div>
</main>
<script>
const articlesData = [
{
path: '~/notes/iot-firmware-spi-extraction.md',
date: '2026-07-20',
time: '8 mins read',
tags: '[#firmware] [#hardware] [#security] [#bus-pirate]',
abstract: 'In security audits of embedded systems, retrieving the firmware is the critical first step. When over-the-air updates are encrypted and debug ports (UART, JTAG) are disabled in production silicon, dumping physical SPI Flash memory becomes necessary. This writeup covers extracting firmware from an 8-pin SOP SPI flash chip using a Bus Pirate and flashrom to bypass bootloader lockdowns.',
code: `# Query the SPI flash details using flashrom via Bus Pirate\nflashrom -p buspirate_spi:dev=/dev/ttyUSB0,spispeed=1M -r firmware_dump.bin\n\n# Verify the integrity of the binary dump by comparing hashes\nif [ $? -eq 0 ]; then\n echo "Dump successful. Running md5sum..."\n md5sum firmware_dump.bin\nfi`
},
{
path: '~/notes/ha-traefik-docker.md',
date: '2026-07-15',
time: '12 mins read',
tags: '[#homelab] [#docker-traefik] [#networking]',
abstract: 'Architecting automated TLS wildcard certificate propagation, Docker provider label routing, zero-downtime container updates, and backend health-check failover routes on bare-metal infrastructure.',
code: `services:\n traefik:\n image: traefik:v3.1\n command:\n - "--providers.docker=true"\n - "--entrypoints.websecure.address=:443"\n - "--certificatesresolvers.cf.acme.dnschallenge=true"`
},
{
path: '~/notes/posix-sh-vs-bash.md',
date: '2026-07-10',
time: '6 mins read',
tags: '[#posix-sh] [#shell] [#unix]',
abstract: 'Analyzing edge-case incompatibilities, syntax overhead, eliminating bashisms, and writing fast, bulletproof bootstrapping scripts for Alpine and embedded targets conforming to IEEE Std 1003.1.',
code: `#!/bin/sh\n# Zero-allocation POSIX argument validation\n[ "$#" -ge 1 ] || { echo "Usage: $0 <target>" >&2; exit 1; }\ntarget="$1"\nprintf 'Configuring target node: %s\\n' "$target"`
},
{
path: '~/notes/ctf-heap-glibc-2-35.md',
date: '2026-06-28',
time: '15 mins read',
tags: '[#ctf] [#binary-exploitation] [#security]',
abstract: 'Deconstructing modern glibc memory allocator defenses: tcache safe-linking pointer mangling, bypass primitives, and House of Apple file structure hijacking for arbitrary code execution.',
code: `// glibc 2.35 tcache safe-linking pointer de-mangling\nuint64_t reveal_ptr(uint64_t mangled_ptr, uint64_t addr) {\n return mangled_ptr ^ (addr >> 12);\n}`
},
{
path: '~/notes/zero-trust-wireguard-opnsense.md',
date: '2026-06-14',
time: '10 mins read',
tags: '[#networking] [#wireguard] [#security]',
abstract: 'Designing strict zone-based firewall rules across isolated VLANs (Management, Server DMZ, IoT, Trusted Clients) with point-to-point WireGuard cryptographic routing.',
code: `[Interface]\nPrivateKey = aGlkZGVuX2tleV9kYXRhX2hlcmU=\nAddress = 10.100.0.5/32\nDNS = 10.100.0.1\n\n[Peer]\nPublicKey = cGVlcl9wdWJsaWNfa2V5X2RhdGFfaGVyZQ==\nEndpoint = vpn.rooks.dev:51820\nAllowedIPs = 10.100.0.0/24`
}
];
function selectRow(index) {
document.querySelectorAll('.directory-row').forEach(row => row.classList.remove('active'));
const activeRow = document.querySelector(`.directory-row[data-index="${index}"]`);
if (activeRow) activeRow.classList.add('active');
const data = articlesData[index];
if (!data) return;
document.getElementById('previewFilePath').innerText = data.path;
document.getElementById('previewMeta').innerText = `${data.date} • ${data.time} • ${data.tags}`;
document.getElementById('previewAbstract').innerText = data.abstract;
document.getElementById('previewCode').innerHTML = `<pre><code>${escapeHtml(data.code)}</code></pre>`;
}
function handleSearch(query) {
const q = query.toLowerCase().trim();
const rows = document.querySelectorAll('.directory-row');
let visibleCount = 0;
rows.forEach(row => {
const text = (row.innerText + ' ' + (row.getAttribute('data-keywords') || '')).toLowerCase();
if (!q || text.includes(q)) {
row.style.display = '';
visibleCount++;
} else {
row.style.display = 'none';
}
});
document.getElementById('matchCounter').innerText = `${visibleCount}/5 matches`;
}
function escapeHtml(str) {
return str
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;");
}
</script>
</body>
</html>
+661
View File
@@ -0,0 +1,661 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Blog Lander (Dual-Lead Grid &amp; Feed)</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Tile surface */
--bg-elevated: #1c1f26; /* Hovered tile surface */
--border-color: #2b303b; /* Crisp 1px border */
--text-primary: #e0e6f0; /* Light off-white prose */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Primary active accent */
--accent-green: #00e676; /* Operational status indicator */
--accent-amber: #ffb700; /* Highlight accent */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px 48px 16px;
}
.tiling-layout {
width: 100%;
max-width: 1120px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Top Global Header Bar (Gateway 3 Match) */
.global-bar {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px 18px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
flex-wrap: wrap;
gap: 10px;
}
.brand-group {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
font-weight: 700;
}
.status-indicator {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
}
.bar-nav {
display: flex;
gap: 14px;
align-items: center;
}
.bar-link {
color: var(--text-muted);
text-decoration: none;
transition: none; /* Instant snap */
}
.bar-link:hover {
color: var(--accent-cyan);
}
.bar-link.active {
color: var(--text-primary);
border-bottom: 1px solid var(--accent-cyan);
}
/* Hero Section Tile */
.hero-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-left: 3px solid var(--accent-cyan);
border-radius: 4px;
padding: 24px 28px;
display: flex;
flex-direction: column;
gap: 12px;
}
.hero-pretitle {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--accent-cyan);
letter-spacing: 0.06em;
text-transform: uppercase;
}
.hero-main-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 2.1rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
}
.hero-landing-text {
font-size: 0.98rem;
color: var(--text-primary);
line-height: 1.65;
max-width: 84ch;
}
.hero-metadata-badges {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
margin-top: 4px;
}
.meta-badge {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
padding: 3px 8px;
border-radius: 2px;
color: var(--text-muted);
}
/* Dual-Lead Grid: 2 Side-by-Side Featured Cards */
.dual-lead-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
}
@media (max-width: 840px) {
.dual-lead-grid {
grid-template-columns: 1fr;
}
}
/* Tile Container Style */
.tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 14px;
}
.tile-header {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 1px solid var(--border-color);
padding-bottom: 10px;
}
.tile-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.88rem;
font-weight: 700;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.tile-meta {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
}
.lead-badge-row {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
}
.lead-heading {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.2rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
line-height: 1.35;
display: inline-block;
transition: none;
}
.lead-heading:hover {
color: var(--accent-cyan);
}
.lead-excerpt {
font-size: 0.88rem;
color: var(--text-primary);
line-height: 1.55;
}
/* Terminal Code Snippet */
.code-preview {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.76rem;
overflow: hidden;
}
.code-preview-header {
background-color: var(--bg-elevated);
border-bottom: 1px solid var(--border-color);
padding: 4px 10px;
font-size: 0.72rem;
color: var(--text-muted);
display: flex;
justify-content: space-between;
}
.code-preview-body {
padding: 10px;
overflow-x: auto;
color: var(--text-primary);
line-height: 1.45;
}
.lead-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10px;
border-top: 1px solid var(--border-color);
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
flex-wrap: wrap;
gap: 8px;
}
.read-lead-btn {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
padding: 5px 12px;
font-weight: 700;
border-radius: 2px;
text-decoration: none;
transition: none;
}
.read-lead-btn:hover {
background-color: transparent;
color: var(--accent-cyan);
box-shadow: inset 0 0 0 1px var(--accent-cyan);
}
/* Archive Dispatches Feed Table */
.archive-feed-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 14px;
}
.archive-table-wrapper {
width: 100%;
overflow-x: auto;
}
.archive-table {
width: 100%;
border-collapse: collapse;
font-size: 0.82rem;
text-align: left;
}
.archive-table th {
padding: 8px 12px;
background-color: var(--bg-canvas);
border-bottom: 1px solid var(--border-color);
color: var(--text-muted);
font-weight: 500;
white-space: nowrap;
font-family: 'IBM Plex Mono', monospace;
}
.archive-table td {
padding: 12px;
border-bottom: 1px solid var(--border-color);
vertical-align: middle;
white-space: nowrap;
}
.archive-row {
cursor: pointer;
transition: none;
}
.archive-row:hover {
background-color: var(--bg-elevated);
}
.archive-row.active {
background-color: var(--bg-elevated);
border-left: 2px solid var(--accent-cyan);
}
.archive-title-link {
font-family: 'IBM Plex Mono', monospace;
color: var(--text-primary);
text-decoration: none;
font-weight: 700;
}
.archive-row:hover .archive-title-link {
color: var(--accent-cyan);
}
/* Interactive Inline Inspection Pane */
.inspect-box {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px;
margin-top: 6px;
}
.inspect-head {
display: flex;
justify-content: space-between;
border-bottom: 1px solid var(--border-color);
padding-bottom: 8px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
}
.inspect-prose {
font-family: 'Inter', sans-serif;
font-size: 0.9rem;
color: var(--text-primary);
line-height: 1.6;
}
/* Global Footer Tile (Gateway 3 Match) */
.footer-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 10px;
}
.footer-links-cluster {
display: flex;
gap: 12px;
align-items: center;
}
.footer-btn {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-btn:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="tiling-layout">
<!-- Top Global Bar (Gateway 3 Match) -->
<header class="global-bar">
<div class="brand-group">
<span class="status-indicator"></span>
<span>RIVER ROOKS // DUAL-LEAD DISPATCH CATALOG</span>
</div>
<nav class="bar-nav" aria-label="Quick Navigation">
<a href="service-gateway-tiling.html" class="bar-link">Gateway 3 ↗</a>
<a href="#" class="bar-link active">&gt; Catalog</a>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="bar-link">Git Forge</a>
<a href="feed.xml" class="bar-link">RSS</a>
<a href="index.html" class="bar-link">Samples ↗</a>
</nav>
</header>
<!-- Hero Tile: Mission Statement & Metadata Badges -->
<section class="hero-tile" aria-label="Blog Declaration">
<div class="hero-pretitle">RESEARCH &bull; DISPATCHES &bull; SPECIFICATIONS</div>
<h1 class="hero-main-title">Systems Research &amp; Security Engineering Feed</h1>
<p class="hero-landing-text">
This catalog organizes field reports across two primary research vectors: hardware firmware reverse engineering and bare-metal self-hosted cloud infrastructure. Full source code, shell bootstrapping scripts, and network topology manifests are included.
</p>
<div class="hero-metadata-badges">
<span class="meta-badge">[NODE: gw-node01.lan]</span>
<span class="meta-badge">[5 CANONICAL DISPATCHES]</span>
<span class="meta-badge">[FORMAT: GHOSTTY / ZED MINIMALISM]</span>
<span class="meta-badge">[ENCRYPTED SYNC: WIREGUARD]</span>
</div>
</section>
<!-- Dual-Lead Grid: 2 Featured Stories Side-by-Side -->
<div class="dual-lead-grid">
<!-- Lead 1: Firmware Reversing -->
<section class="tile" aria-label="Lead Article 1">
<div class="tile-header">
<div class="tile-title">
<span>// LEAD: HARDWARE FORENSICS</span>
</div>
<span class="tile-meta">2026-07-20 &bull; 8 MIN</span>
</div>
<div style="display: flex; flex-direction: column; gap: 10px;">
<div class="lead-badge-row">
<span style="color: var(--accent-amber); font-weight: 700; font-family: 'IBM Plex Mono', monospace;">[PINNED LEAD]</span>
<span style="color: var(--text-muted); font-family: 'IBM Plex Mono', monospace;">~/notes/iot-firmware-spi-extraction.md</span>
</div>
<h2 style="margin: 0;">
<a href="#" class="lead-heading">Reverse Engineering IoT Firmware: SPI Flash Extraction</a>
</h2>
<p class="lead-excerpt">
Extracting binary firmware from an 8-pin SOP SPI flash chip using a Bus Pirate and flashrom to bypass locked-down UART/JTAG interfaces on production microcontrollers.
</p>
<div class="code-preview">
<div class="code-preview-header">
<span>extract_firmware.sh</span>
<span>POSIX sh</span>
</div>
<div class="code-preview-body">
<pre><code>flashrom -p buspirate_spi:dev=/dev/ttyUSB0,spispeed=1M \
-r firmware_dump.bin && md5sum firmware_dump.bin</code></pre>
</div>
</div>
</div>
<div class="lead-footer">
<span style="color: var(--text-muted);">[#firmware] [#hardware]</span>
<a href="#" class="read-lead-btn">READ WRITEUP [8 MIN] &rarr;</a>
</div>
</section>
<!-- Lead 2: Traefik Infrastructure -->
<section class="tile" aria-label="Lead Article 2">
<div class="tile-header">
<div class="tile-title">
<span>// LEAD: INFRASTRUCTURE ROUTING</span>
</div>
<span class="tile-meta">2026-07-15 &bull; 12 MIN</span>
</div>
<div style="display: flex; flex-direction: column; gap: 10px;">
<div class="lead-badge-row">
<span style="color: var(--accent-cyan); font-weight: 700; font-family: 'IBM Plex Mono', monospace;">[PINNED LEAD]</span>
<span style="color: var(--text-muted); font-family: 'IBM Plex Mono', monospace;">~/notes/ha-traefik-docker.md</span>
</div>
<h2 style="margin: 0;">
<a href="#" class="lead-heading">High-Availability Traefik Router with Docker Compose</a>
</h2>
<p class="lead-excerpt">
Architecting automated TLS wildcard certificate propagation, Docker provider label routing, zero-downtime container updates, and backend health-check failover.
</p>
<div class="code-preview">
<div class="code-preview-header">
<span>docker-compose.traefik.yml</span>
<span>YAML</span>
</div>
<div class="code-preview-body">
<pre><code>services:
traefik:
image: traefik:v3.1
command: ["--providers.docker=true", "--entrypoints.websecure.address=:443"]</code></pre>
</div>
</div>
</div>
<div class="lead-footer">
<span style="color: var(--text-muted);">[#homelab] [#docker-traefik]</span>
<a href="#" class="read-lead-btn">READ WRITEUP [12 MIN] &rarr;</a>
</div>
</section>
</div>
<!-- Archive Dispatches Feed Tile with Interactive Inspector -->
<section class="archive-feed-tile" aria-label="Chronological Dispatches Archive">
<div class="tile-header">
<div class="tile-title">
<span>// CHRONOLOGICAL DISPATCHES ARCHIVE</span>
</div>
<span class="tile-meta">SELECT ROW TO INSPECT NOTES</span>
</div>
<div class="archive-table-wrapper">
<table class="archive-table" aria-label="Archive table">
<thead>
<tr>
<th scope="col">DATE</th>
<th scope="col">CATEGORY</th>
<th scope="col">TITLE / PATH</th>
<th scope="col">READ TIME</th>
<th scope="col">ACTION</th>
</tr>
</thead>
<tbody>
<tr class="archive-row active" onclick="inspectRow(0)">
<td><span style="color: var(--accent-cyan); font-family: 'IBM Plex Mono', monospace;">2026-07-10</span></td>
<td><span style="color: var(--text-muted); font-family: 'IBM Plex Mono', monospace;">[#posix-sh]</span></td>
<td><a href="#" class="archive-title-link">&gt; POSIX sh vs. Bash: Portability in Modern Scripts</a></td>
<td><span style="color: var(--text-muted); font-family: 'IBM Plex Mono', monospace;">6 min</span></td>
<td><span style="color: var(--accent-cyan); font-family: 'IBM Plex Mono', monospace;">[INSPECT &darr;]</span></td>
</tr>
<tr class="archive-row" onclick="inspectRow(1)">
<td><span style="color: var(--accent-cyan); font-family: 'IBM Plex Mono', monospace;">2026-06-28</span></td>
<td><span style="color: var(--text-muted); font-family: 'IBM Plex Mono', monospace;">[#ctf]</span></td>
<td><a href="#" class="archive-title-link">&gt; CTF Write-up: Heap Exploitation in glibc 2.35</a></td>
<td><span style="color: var(--text-muted); font-family: 'IBM Plex Mono', monospace;">15 min</span></td>
<td><span style="color: var(--accent-cyan); font-family: 'IBM Plex Mono', monospace;">[INSPECT &darr;]</span></td>
</tr>
<tr class="archive-row" onclick="inspectRow(2)">
<td><span style="color: var(--accent-cyan); font-family: 'IBM Plex Mono', monospace;">2026-06-14</span></td>
<td><span style="color: var(--text-muted); font-family: 'IBM Plex Mono', monospace;">[#networking]</span></td>
<td><a href="#" class="archive-title-link">&gt; Zero-Trust Network Isolation with WireGuard &amp; OPNsense VLANs</a></td>
<td><span style="color: var(--text-muted); font-family: 'IBM Plex Mono', monospace;">10 min</span></td>
<td><span style="color: var(--accent-cyan); font-family: 'IBM Plex Mono', monospace;">[INSPECT &darr;]</span></td>
</tr>
</tbody>
</table>
</div>
<!-- Inspection Details Box -->
<div class="inspect-box" id="inspectBox">
<div class="inspect-head">
<span id="inspectPath" style="color: var(--accent-cyan); font-weight: 700;">~/notes/posix-sh-vs-bash.md</span>
<span id="inspectMeta">2026-07-10 &bull; 6 MIN READ &bull; [#posix-sh] [#shell] [#unix]</span>
</div>
<p class="inspect-prose" id="inspectProse">
Analyzing edge-case incompatibilities, syntax overhead, eliminating bashisms, and writing fast, bulletproof bootstrapping scripts for Alpine and embedded targets conforming to IEEE Std 1003.1.
</p>
<div style="display: flex; justify-content: flex-end;">
<a href="#" id="inspectReadBtn" class="read-lead-btn" style="padding: 4px 10px; font-size: 0.76rem;">OPEN ENTRY &rarr;</a>
</div>
</div>
</section>
<!-- Global Footer Tile (Matching Gateway 3 Exactly) -->
<footer class="footer-tile">
<div>River Rooks &bull; University of Alabama College of Engineering</div>
<nav class="footer-links-cluster" aria-label="Global Footer Links">
<a href="service-gateway-tiling.html" class="footer-btn">Gateway Lander</a>
<span class="pipe">|</span>
<a href="feed.xml" class="footer-btn">RSS</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-btn">Forgejo</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-btn">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="#" title="Fingerprint: 0x4A8E92F1C0B3D4E7" class="footer-btn">PGP</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-btn">Catalog ↗</a>
</nav>
</footer>
</main>
<script>
const archiveData = [
{
path: '~/notes/posix-sh-vs-bash.md',
meta: '2026-07-10 • 6 MIN READ • [#posix-sh] [#shell] [#unix]',
prose: 'Analyzing edge-case incompatibilities, syntax overhead, eliminating bashisms, and writing fast, bulletproof bootstrapping scripts for Alpine and embedded targets conforming to IEEE Std 1003.1.'
},
{
path: '~/notes/ctf-heap-glibc-2-35.md',
meta: '2026-06-28 • 15 MIN READ • [#ctf] [#binary-exploitation] [#security]',
prose: 'Deconstructing modern glibc memory allocator defenses: tcache safe-linking pointer mangling, bypass primitives, and House of Apple file structure hijacking for arbitrary code execution.'
},
{
path: '~/notes/zero-trust-wireguard-opnsense.md',
meta: '2026-06-14 • 10 MIN READ • [#networking] [#wireguard] [#security]',
prose: 'Designing strict zone-based firewall rules across isolated VLANs (Management, Server DMZ, IoT, Trusted Clients) with point-to-point WireGuard cryptographic routing.'
}
];
function inspectRow(idx) {
document.querySelectorAll('.archive-row').forEach(row => row.classList.remove('active'));
const activeRow = document.querySelectorAll('.archive-row')[idx];
if (activeRow) activeRow.classList.add('active');
const data = archiveData[idx];
if (data) {
document.getElementById('inspectPath').innerText = data.path;
document.getElementById('inspectMeta').innerText = data.meta;
document.getElementById('inspectProse').innerText = data.prose;
}
}
</script>
</body>
</html>
+780
View File
@@ -0,0 +1,780 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Blog Lander (Tiling Hero &amp; Feed)</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Tile surface */
--bg-elevated: #1c1f26; /* Hovered tile surface */
--border-color: #2b303b; /* Crisp 1px solid border */
--text-primary: #e0e6f0; /* Light off-white prose */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Primary active accent */
--accent-green: #00e676; /* Operational status indicator */
--accent-amber: #ffb700; /* Warning / highlight accent */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px 48px 16px;
}
.tiling-layout {
width: 100%;
max-width: 1120px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Top Global Status Header Bar (Gateway 3 Style) */
.global-bar {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px 18px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
flex-wrap: wrap;
gap: 10px;
}
.brand-group {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
font-weight: 700;
}
.status-indicator {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
}
.bar-nav {
display: flex;
gap: 14px;
align-items: center;
}
.bar-link {
color: var(--text-muted);
text-decoration: none;
transition: none; /* Instant snap */
}
.bar-link:hover {
color: var(--accent-cyan);
}
.bar-link.active {
color: var(--text-primary);
border-bottom: 1px solid var(--accent-cyan);
}
/* Hero Tile: Section Declaration & Synopsis */
.hero-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-left: 3px solid var(--accent-cyan);
border-radius: 4px;
padding: 24px 28px;
display: flex;
flex-direction: column;
gap: 12px;
}
.hero-pretitle {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--accent-cyan);
letter-spacing: 0.06em;
text-transform: uppercase;
}
.hero-main-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 2.1rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
line-height: 1.2;
}
.hero-landing-text {
font-size: 0.98rem;
color: var(--text-primary);
line-height: 1.65;
max-width: 82ch;
}
.filter-pills-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-top: 4px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
}
.filter-tag-btn {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
color: var(--text-muted);
padding: 3px 8px;
border-radius: 2px;
cursor: pointer;
transition: none;
}
.filter-tag-btn:hover {
border-color: var(--accent-cyan);
color: var(--text-primary);
background-color: var(--bg-elevated);
}
.filter-tag-btn.active {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
border-color: var(--accent-cyan);
font-weight: 700;
}
/* Asymmetric Split Grid */
.split-grid {
display: grid;
grid-template-columns: 1.25fr 0.75fr;
gap: 16px;
}
@media (max-width: 880px) {
.split-grid {
grid-template-columns: 1fr;
}
}
/* Standard Tile Container */
.tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 14px;
}
.tile-header {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 1px solid var(--border-color);
padding-bottom: 10px;
}
.tile-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.92rem;
font-weight: 700;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.tile-meta {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
}
/* Featured Lead Article Card (Inside Tile) */
.featured-card-body {
display: flex;
flex-direction: column;
gap: 12px;
}
.featured-badge-row {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
flex-wrap: wrap;
gap: 8px;
}
.badge-pinned {
color: var(--accent-amber);
font-weight: 700;
}
.featured-article-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.35rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
line-height: 1.3;
transition: none;
}
.featured-article-title:hover {
color: var(--accent-cyan);
}
.featured-prose {
font-size: 0.92rem;
color: var(--text-primary);
line-height: 1.6;
}
/* Terminal Code Block Component */
.terminal-code-box {
border: 1px solid var(--border-color);
border-radius: 3px;
background-color: var(--bg-canvas);
overflow: hidden;
font-family: 'JetBrains Mono', monospace;
}
.code-titlebar {
background-color: var(--bg-surface);
border-bottom: 1px solid var(--border-color);
padding: 6px 12px;
font-size: 0.74rem;
color: var(--text-muted);
display: flex;
justify-content: space-between;
}
.code-content {
padding: 12px;
font-size: 0.78rem;
overflow-x: auto;
color: var(--text-primary);
line-height: 1.5;
}
.featured-cta-row {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 10px;
border-top: 1px solid var(--border-color);
flex-wrap: wrap;
gap: 10px;
font-family: 'IBM Plex Mono', monospace;
}
.cta-button {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
padding: 6px 14px;
font-weight: 700;
font-size: 0.8rem;
text-decoration: none;
border-radius: 2px;
border: 1px solid var(--accent-cyan);
transition: none;
}
.cta-button:hover {
background-color: transparent;
color: var(--accent-cyan);
}
/* Sidebar Tile Elements */
.sidebar-stats-box {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 12px 14px;
display: flex;
flex-direction: column;
gap: 8px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
}
.stat-row {
display: flex;
justify-content: space-between;
}
.stat-val {
color: var(--text-primary);
font-weight: 700;
}
.topic-track-item {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 4px;
text-decoration: none;
transition: none;
}
.topic-track-item:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.topic-track-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.84rem;
font-weight: 700;
color: var(--text-primary);
}
.topic-track-item:hover .topic-track-title {
color: var(--accent-cyan);
}
.topic-track-desc {
font-size: 0.78rem;
color: var(--text-muted);
line-height: 1.4;
}
/* Full-Width Chronological Article Feed Tile */
.feed-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.feed-card {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 16px 18px;
display: flex;
flex-direction: column;
gap: 8px;
transition: none;
}
.feed-card:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.feed-meta-row {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
color: var(--text-muted);
}
.feed-date {
color: var(--accent-cyan);
}
.feed-heading {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.1rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
line-height: 1.35;
transition: none;
}
.feed-card:hover .feed-heading {
color: var(--accent-cyan);
}
.feed-excerpt {
font-size: 0.9rem;
color: var(--text-primary);
line-height: 1.6;
}
.feed-tags-row {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 6px;
border-top: 1px solid var(--border-color);
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 6px;
}
.tag-cluster {
display: flex;
gap: 8px;
}
/* Global Footer Tile (Gateway 3 Standard) */
.footer-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 10px;
}
.footer-links-cluster {
display: flex;
gap: 12px;
align-items: center;
}
.footer-btn {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-btn:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="tiling-layout">
<!-- Top Global Status Bar (Gateway 3 Match) -->
<header class="global-bar">
<div class="brand-group">
<span class="status-indicator"></span>
<span>RIVER ROOKS // BLOG &amp; RESEARCH ARCHIVE</span>
</div>
<nav class="bar-nav" aria-label="Quick Navigation">
<a href="service-gateway-tiling.html" class="bar-link">Gateway 3 ↗</a>
<a href="#" class="bar-link active">&gt; Dispatches</a>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="bar-link">Git Forge</a>
<a href="feed.xml" class="bar-link">RSS Feed</a>
<a href="index.html" class="bar-link">Catalog ↗</a>
</nav>
</header>
<!-- Hero Tile: Blog Mission & Topic Filters -->
<section class="hero-tile" aria-label="Blog Mission & Overview">
<div class="hero-pretitle">TECHNICAL PUBLICATION REPOSITORY &bull; ~/NOTES/INDEX.MD</div>
<h1 class="hero-main-title">Engineering Notes, CTF Write-Ups &amp; Systems Architecture</h1>
<p class="hero-landing-text">
Documenting bare-metal systems, embedded hardware audits, and network isolation architectures. Writing zero-allocation POSIX scripts, reverse engineering production microcontrollers, and analyzing memory allocator exploitation primitives.
</p>
<div class="filter-pills-row">
<span style="color: var(--text-muted); margin-right: 4px;">TOPIC FILTER:</span>
<button class="filter-tag-btn active" onclick="filterTopic('all', this)">[All Dispatches (5)]</button>
<button class="filter-tag-btn" onclick="filterTopic('firmware', this)">[#firmware (1)]</button>
<button class="filter-tag-btn" onclick="filterTopic('homelab', this)">[#homelab (2)]</button>
<button class="filter-tag-btn" onclick="filterTopic('security', this)">[#security (3)]</button>
<button class="filter-tag-btn" onclick="filterTopic('posix-sh', this)">[#posix-sh (1)]</button>
</div>
</section>
<!-- Split Grid: Featured Article + Research Index -->
<div class="split-grid">
<!-- Left Column: Featured Article Tile -->
<section class="tile" aria-label="Featured Technical Article">
<div class="tile-header">
<div class="tile-title">
<span>// PINNED RESEARCH WRITE-UP</span>
</div>
<span class="tile-meta">SPI FLASH &bull; 8 MIN READ</span>
</div>
<div class="featured-card-body">
<div class="featured-badge-row">
<span class="badge-pinned">[FEATURED DEEP DIVE]</span>
<span style="color: var(--text-muted);">2026-07-20 &bull; ~/notes/iot-firmware-spi-extraction.md</span>
</div>
<h2 style="margin: 0;">
<a href="article-tiling-firmware.html" class="featured-article-title">Reverse Engineering IoT Firmware: SPI Flash Extraction</a>
</h2>
<p class="featured-prose">
In security audits of embedded systems, retrieving firmware is the critical first step. When over-the-air updates are encrypted and debug ports (UART, JTAG) are disabled in production silicon, dumping physical SPI Flash memory becomes necessary. This writeup covers extracting firmware from an 8-pin SOP SPI flash chip using a Bus Pirate and flashrom to bypass bootloader lockdowns.
</p>
<!-- Terminal Code Box -->
<div class="terminal-code-box">
<div class="code-titlebar">
<span>extract_firmware.sh</span>
<span>POSIX sh</span>
</div>
<div class="code-content">
<pre><code># Query the SPI flash details using flashrom via Bus Pirate
flashrom -p buspirate_spi:dev=/dev/ttyUSB0,spispeed=1M -r firmware_dump.bin
# Verify the integrity of the binary dump by comparing hashes
if [ $? -eq 0 ]; then
echo "Dump successful. Running md5sum..."
md5sum firmware_dump.bin
fi</code></pre>
</div>
</div>
<div class="featured-cta-row">
<div class="tag-cluster">
<span style="color: var(--text-muted); font-size: 0.75rem;">[#firmware] [#hardware] [#security] [#bus-pirate]</span>
</div>
<a href="article-tiling-firmware.html" class="cta-button">READ FULL WRITEUP &rarr;</a>
</div>
</div>
</section>
<!-- Right Column: Quick Research Tracks & Telemetry -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<!-- Publication Stats Tile -->
<section class="tile" aria-label="Archive Statistics">
<div class="tile-header">
<div class="tile-title">
<span>// ARCHIVE TELEMETRY</span>
</div>
<span class="tile-meta">STATIC HTML</span>
</div>
<div class="sidebar-stats-box">
<div class="stat-row">
<span>TOTAL ARTICLES:</span>
<span class="stat-val">5 PUBLISHED</span>
</div>
<div class="stat-row">
<span>TOTAL READING TIME:</span>
<span class="stat-val">51 MINUTES</span>
</div>
<div class="stat-row">
<span>ACTIVE TOPICS:</span>
<span class="stat-val">7 CATEGORIES</span>
</div>
<div class="stat-row">
<span>FORMAT COMPLIANCE:</span>
<span class="stat-val">IEEE STD 1003.1</span>
</div>
</div>
</section>
<!-- Curated Focus Tracks -->
<section class="tile" aria-label="Curated Topic Tracks">
<div class="tile-header">
<div class="tile-title">
<span>// RESEARCH TRACKS</span>
</div>
<span class="tile-meta">CURATED</span>
</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
<a href="#" class="topic-track-item">
<div class="topic-track-title">&gt; Embedded &amp; Hardware Forensics</div>
<div class="topic-track-desc">Flash memory dumping, bus tapping, silicon pinout discovery.</div>
</a>
<a href="#" class="topic-track-item">
<div class="topic-track-title">&gt; Network Routing &amp; Homelab</div>
<div class="topic-track-desc">Traefik reverse proxying, WireGuard full-mesh, VLAN firewalls.</div>
</a>
<a href="#" class="topic-track-item">
<div class="topic-track-title">&gt; Low-Level Memory Exploitation</div>
<div class="topic-track-desc">glibc tcache safe-linking, heap corruption, House of Apple.</div>
</a>
</div>
</section>
</div>
</div>
<!-- Full-Width Article Feed Tile -->
<section class="tile" aria-label="Chronological Article Stream">
<div class="tile-header">
<div class="tile-title">
<span>// CHRONOLOGICAL DISPATCHES FEED</span>
</div>
<span class="tile-meta">ALL ARTICLES ORDERED BY DATE</span>
</div>
<div class="feed-list" id="feedList">
<!-- Post 2: Traefik -->
<article class="feed-card" data-tags="homelab networking">
<div class="feed-meta-row">
<time datetime="2026-07-15" class="feed-date">2026-07-15 &bull; 12 min read</time>
<span>~/notes/ha-traefik-docker.md</span>
</div>
<h3 style="margin: 0;">
<a href="article-tiling-traefik.html" class="feed-heading">&gt; Building a High-Availability Traefik Router with Docker Compose</a>
</h3>
<p class="feed-excerpt">
Architecting automated TLS wildcard certificate propagation, Docker provider label routing, zero-downtime container updates, and backend health-check failover routes on bare-metal infrastructure.
</p>
<div class="feed-tags-row">
<div class="tag-cluster">
<span>[#homelab]</span>
<span>[#docker-traefik]</span>
<span>[#networking]</span>
</div>
<a href="article-tiling-traefik.html" style="color: var(--accent-cyan); text-decoration: none;">Read Entry &rarr;</a>
</div>
</article>
<!-- Post 3: POSIX sh -->
<article class="feed-card" data-tags="posix-sh security">
<div class="feed-meta-row">
<time datetime="2026-07-10" class="feed-date">2026-07-10 &bull; 6 min read</time>
<span>~/notes/posix-sh-vs-bash.md</span>
</div>
<h3 style="margin: 0;">
<a href="#" class="feed-heading">&gt; POSIX sh vs. Bash: Portability in Modern Scripts</a>
</h3>
<p class="feed-excerpt">
Analyzing edge-case incompatibilities, syntax overhead, eliminating bashisms, and writing fast, bulletproof bootstrapping scripts for Alpine and embedded targets conforming to IEEE Std 1003.1.
</p>
<div class="feed-tags-row">
<div class="tag-cluster">
<span>[#posix-sh]</span>
<span>[#shell]</span>
<span>[#unix]</span>
</div>
<a href="#" style="color: var(--accent-cyan); text-decoration: none;">Read Entry &rarr;</a>
</div>
</article>
<!-- Post 4: Heap Exploitation -->
<article class="feed-card" data-tags="security">
<div class="feed-meta-row">
<time datetime="2026-06-28" class="feed-date">2026-06-28 &bull; 15 min read</time>
<span>~/notes/ctf-heap-glibc-2-35.md</span>
</div>
<h3 style="margin: 0;">
<a href="article-tiling-heap.html" class="feed-heading">&gt; CTF Write-up: Heap Exploitation in glibc 2.35</a>
</h3>
<p class="feed-excerpt">
Deconstructing modern glibc memory allocator defenses: tcache safe-linking pointer mangling, bypass primitives, and House of Apple file structure hijacking for arbitrary code execution.
</p>
<div class="feed-tags-row">
<div class="tag-cluster">
<span>[#ctf]</span>
<span>[#binary-exploitation]</span>
<span>[#security]</span>
</div>
<a href="article-tiling-heap.html" style="color: var(--accent-cyan); text-decoration: none;">Read Entry &rarr;</a>
</div>
</article>
<!-- Post 5: Zero-Trust WireGuard -->
<article class="feed-card" data-tags="networking security homelab">
<div class="feed-meta-row">
<time datetime="2026-06-14" class="feed-date">2026-06-14 &bull; 10 min read</time>
<span>~/notes/zero-trust-wireguard-opnsense.md</span>
</div>
<h3 style="margin: 0;">
<a href="#" class="feed-heading">&gt; Zero-Trust Network Isolation with WireGuard &amp; OPNsense VLANs</a>
</h3>
<p class="feed-excerpt">
Designing strict zone-based firewall rules across isolated VLANs (Management, Server DMZ, IoT, Trusted Clients) with point-to-point WireGuard cryptographic routing.
</p>
<div class="feed-tags-row">
<div class="tag-cluster">
<span>[#networking]</span>
<span>[#wireguard]</span>
<span>[#security]</span>
</div>
<a href="#" style="color: var(--accent-cyan); text-decoration: none;">Read Entry &rarr;</a>
</div>
</article>
</div>
</section>
<!-- Global Footer Tile (Matching Gateway 3 Exactly) -->
<footer class="footer-tile">
<div>River Rooks &bull; University of Alabama College of Engineering</div>
<nav class="footer-links-cluster" aria-label="Global Footer Links">
<a href="service-gateway-tiling.html" class="footer-btn">Gateway Lander</a>
<span class="pipe">|</span>
<a href="feed.xml" class="footer-btn">RSS</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-btn">Forgejo</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-btn">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="#" title="Fingerprint: 0x4A8E92F1C0B3D4E7" class="footer-btn">PGP</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-btn">Catalog ↗</a>
</nav>
</footer>
</main>
<script>
function filterTopic(topic, btn) {
document.querySelectorAll('.filter-tag-btn').forEach(el => el.classList.remove('active'));
btn.classList.add('active');
const cards = document.querySelectorAll('#feedList .feed-card');
cards.forEach(card => {
if (topic === 'all') {
card.style.display = 'flex';
} else {
const tags = card.getAttribute('data-tags') || '';
if (tags.includes(topic)) {
card.style.display = 'flex';
} else {
card.style.display = 'none';
}
}
});
}
</script>
</body>
</html>
+711
View File
@@ -0,0 +1,711 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Blog Lander (Asymmetric 3-Tile Workspace)</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Tile surface */
--bg-elevated: #1c1f26; /* Hovered tile surface */
--border-color: #2b303b; /* Crisp 1px solid border */
--text-primary: #e0e6f0; /* Light off-white prose */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Primary active accent */
--accent-green: #00e676; /* Operational status indicator */
--accent-amber: #ffb700; /* Pinned highlight accent */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px 48px 16px;
}
.tiling-layout {
width: 100%;
max-width: 1120px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Top Global Header Bar (Gateway 3 Standard) */
.global-bar {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px 18px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
flex-wrap: wrap;
gap: 10px;
}
.brand-group {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
font-weight: 700;
}
.status-indicator {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
}
.bar-nav {
display: flex;
gap: 14px;
align-items: center;
}
.bar-link {
color: var(--text-muted);
text-decoration: none;
transition: none; /* Instant snap */
}
.bar-link:hover {
color: var(--accent-cyan);
}
.bar-link.active {
color: var(--text-primary);
border-bottom: 1px solid var(--accent-cyan);
}
/* Hero Manifesto Tile */
.hero-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-left: 3px solid var(--accent-cyan);
border-radius: 4px;
padding: 24px 28px;
display: flex;
flex-direction: column;
gap: 12px;
}
.hero-pretitle {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--accent-cyan);
letter-spacing: 0.06em;
text-transform: uppercase;
}
.hero-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 2.1rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
}
.hero-prose {
font-size: 0.98rem;
color: var(--text-primary);
line-height: 1.65;
max-width: 84ch;
}
/* Interactive Grep Bar */
.grep-bar {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 8px 14px;
display: flex;
align-items: center;
gap: 10px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.82rem;
}
.grep-prompt {
color: var(--accent-cyan);
font-weight: 700;
}
.grep-input {
background: transparent;
border: none;
outline: none;
color: var(--text-primary);
font-family: inherit;
font-size: 0.82rem;
flex: 1;
}
/* Main Asymmetric Split Grid */
.split-grid {
display: grid;
grid-template-columns: 1.25fr 0.75fr;
gap: 16px;
}
@media (max-width: 880px) {
.split-grid {
grid-template-columns: 1fr;
}
}
/* Standard Tile Container */
.tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 14px;
}
.tile-header {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 1px solid var(--border-color);
padding-bottom: 10px;
}
.tile-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.92rem;
font-weight: 700;
color: var(--text-primary);
}
.tile-meta {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
}
/* Featured Entry Card inside Primary Column */
.featured-entry {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
.featured-entry:hover {
border-color: var(--accent-cyan);
}
.entry-badge-row {
display: flex;
justify-content: space-between;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.75rem;
}
.entry-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.2rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
line-height: 1.35;
transition: none;
}
.entry-title:hover {
color: var(--accent-cyan);
}
.entry-abstract {
font-size: 0.9rem;
color: var(--text-primary);
line-height: 1.6;
}
/* Code Preview Box */
.snippet-box {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 3px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.76rem;
overflow: hidden;
}
.snippet-head {
background-color: var(--bg-canvas);
border-bottom: 1px solid var(--border-color);
padding: 4px 10px;
color: var(--text-muted);
display: flex;
justify-content: space-between;
font-size: 0.72rem;
}
.snippet-body {
padding: 10px;
overflow-x: auto;
color: var(--text-primary);
}
.entry-footer-row {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 8px;
border-top: 1px solid var(--border-color);
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
}
.read-pill {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
padding: 4px 10px;
font-weight: 700;
border-radius: 2px;
text-decoration: none;
transition: none;
}
.read-pill:hover {
background-color: transparent;
color: var(--accent-cyan);
box-shadow: inset 0 0 0 1px var(--accent-cyan);
}
/* Right Column Component: Track Cards */
.track-tile-item {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 12px;
display: flex;
flex-direction: column;
gap: 6px;
text-decoration: none;
transition: none;
}
.track-tile-item:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.track-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.85rem;
font-weight: 700;
color: var(--text-primary);
}
.track-tile-item:hover .track-title {
color: var(--accent-cyan);
}
.track-meta {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.72rem;
color: var(--accent-cyan);
}
.track-desc {
font-size: 0.8rem;
color: var(--text-muted);
line-height: 1.45;
}
/* Right Column: Mini Feed Items */
.compact-feed-item {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.compact-feed-item:hover {
border-color: var(--accent-cyan);
}
.compact-date {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--accent-cyan);
}
.compact-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.92rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
line-height: 1.35;
}
.compact-title:hover {
color: var(--accent-cyan);
}
/* Global Footer Tile (Gateway 3 Match) */
.footer-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 10px;
}
.footer-links-cluster {
display: flex;
gap: 12px;
align-items: center;
}
.footer-btn {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-btn:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="tiling-layout">
<!-- Top Global Bar (Matching Gateway 3 Exactly) -->
<header class="global-bar">
<div class="brand-group">
<span class="status-indicator"></span>
<span>RIVER ROOKS // TECHNICAL NOTES WORKSPACE</span>
</div>
<nav class="bar-nav" aria-label="Top Navigation">
<a href="service-gateway-tiling.html" class="bar-link">Gateway 3 ↗</a>
<a href="#" class="bar-link active">&gt; Field Notes</a>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="bar-link">Git Forge</a>
<a href="feed.xml" class="bar-link">RSS Feed</a>
<a href="index.html" class="bar-link">Catalog ↗</a>
</nav>
</header>
<!-- Hero Tile: Mission Statement & Live Filter -->
<section class="hero-tile" aria-label="Notes Mission">
<div class="hero-pretitle">SYSTEM DISPATCHES &bull; KNOWLEDGE BASE</div>
<h1 class="hero-title">Technical Notes &amp; Systems Architecture</h1>
<p class="hero-prose">
Deep technical documentation on network forensics, self-hosted hypervisor infrastructure, embedded hardware exploitation, and minimal Unix tool design.
</p>
<div class="grep-bar">
<span class="grep-prompt">dev-blog@system:~$</span>
<span style="color: var(--text-muted);">grep -i</span>
<input
type="text"
id="liveFilterInput"
class="grep-input"
placeholder="filter dispatches by keyword, hardware, or ctf..."
oninput="filterArticles(this.value)"
>
<span id="matchStats" style="color: var(--accent-green); font-size: 0.74rem;">5 dispatches loaded</span>
</div>
</section>
<!-- Asymmetric Split Grid -->
<div class="split-grid">
<!-- Left Column: Featured Articles & Primary Feed -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<!-- Tile: Featured Article 1 (Hardware Lead) -->
<section class="tile article-container" data-keywords="firmware hardware security spi bus-pirate flash extraction" aria-label="Lead Article 1">
<div class="tile-header">
<div class="tile-title">
<span>// FEATURED WRITE-UP 01</span>
</div>
<span class="tile-meta">2026-07-20 &bull; 8 MIN READ</span>
</div>
<div class="featured-entry">
<div class="entry-badge-row">
<span style="color: var(--accent-amber); font-weight: 700;">[PINNED RESEARCH]</span>
<span style="color: var(--text-muted);">~/notes/iot-firmware-spi-extraction.md</span>
</div>
<h2 style="margin: 0;">
<a href="#" class="entry-title">&gt; Reverse Engineering IoT Firmware: SPI Flash Extraction</a>
</h2>
<p class="entry-abstract">
In security audits of embedded systems, retrieving the firmware is the critical first step. When over-the-air updates are encrypted and debug ports (UART, JTAG) are disabled in production silicon, dumping physical SPI Flash memory becomes necessary. This writeup covers extracting firmware from an 8-pin SOP SPI flash chip using a Bus Pirate and flashrom.
</p>
<div class="snippet-box">
<div class="snippet-head">
<span>extract_firmware.sh</span>
<span>POSIX sh</span>
</div>
<div class="snippet-body">
<pre><code># Query the SPI flash details using flashrom via Bus Pirate
flashrom -p buspirate_spi:dev=/dev/ttyUSB0,spispeed=1M -r firmware_dump.bin
# Verify the integrity of the binary dump by comparing hashes
if [ $? -eq 0 ]; then
echo "Dump successful. Running md5sum..."
md5sum firmware_dump.bin
fi</code></pre>
</div>
</div>
<div class="entry-footer-row">
<span style="color: var(--text-muted);">[#firmware] [#hardware] [#security] [#bus-pirate]</span>
<a href="#" class="read-pill">READ WRITEUP [8 MIN] &rarr;</a>
</div>
</div>
</section>
<!-- Tile: Featured Article 2 (Infrastructure Lead) -->
<section class="tile article-container" data-keywords="homelab docker traefik networking compose proxy acme" aria-label="Lead Article 2">
<div class="tile-header">
<div class="tile-title">
<span>// FEATURED WRITE-UP 02</span>
</div>
<span class="tile-meta">2026-07-15 &bull; 12 MIN READ</span>
</div>
<div class="featured-entry">
<div class="entry-badge-row">
<span style="color: var(--accent-cyan); font-weight: 700;">[INFRASTRUCTURE SPEC]</span>
<span style="color: var(--text-muted);">~/notes/ha-traefik-docker.md</span>
</div>
<h2 style="margin: 0;">
<a href="#" class="entry-title">&gt; Building a High-Availability Traefik Router with Docker Compose</a>
</h2>
<p class="entry-abstract">
Architecting automated TLS wildcard certificate propagation, Docker provider label routing, zero-downtime container updates, and backend health-check failover routes on bare-metal infrastructure.
</p>
<div class="snippet-box">
<div class="snippet-head">
<span>traefik-compose.yml</span>
<span>YAML</span>
</div>
<div class="snippet-body">
<pre><code>services:
traefik:
image: traefik:v3.1
command:
- "--providers.docker=true"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.cf.acme.dnschallenge=true"</code></pre>
</div>
</div>
<div class="entry-footer-row">
<span style="color: var(--text-muted);">[#homelab] [#docker-traefik] [#networking]</span>
<a href="#" class="read-pill">READ WRITEUP [12 MIN] &rarr;</a>
</div>
</div>
</section>
<!-- Tile: Featured Article 3 (Unix Systems) -->
<section class="tile article-container" data-keywords="posix-sh shell unix portability alpine scripts bashisms" aria-label="Lead Article 3">
<div class="tile-header">
<div class="tile-title">
<span>// SYSTEMS &amp; SCRIPTING DISPATCH</span>
</div>
<span class="tile-meta">2026-07-10 &bull; 6 MIN READ</span>
</div>
<div class="featured-entry">
<div class="entry-badge-row">
<span style="color: var(--accent-green); font-weight: 700;">[STANDARDS &amp; COMPLIANCE]</span>
<span style="color: var(--text-muted);">~/notes/posix-sh-vs-bash.md</span>
</div>
<h2 style="margin: 0;">
<a href="#" class="entry-title">&gt; POSIX sh vs. Bash: Portability in Modern Scripts</a>
</h2>
<p class="entry-abstract">
Analyzing edge-case incompatibilities, syntax overhead, eliminating bashisms, and writing fast, bulletproof bootstrapping scripts for Alpine and embedded targets conforming to IEEE Std 1003.1.
</p>
<div class="entry-footer-row">
<span style="color: var(--text-muted);">[#posix-sh] [#shell] [#unix]</span>
<a href="#" class="read-pill">READ WRITEUP [6 MIN] &rarr;</a>
</div>
</div>
</section>
</div>
<!-- Right Column: Curated Tracks & Additional Dispatches -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<!-- Curated Topic Tracks Tile -->
<section class="tile" aria-label="Curated Topic Tracks">
<div class="tile-header">
<div class="tile-title">
<span>// CURATED TRACKS</span>
</div>
<span class="tile-meta">SPECIALIZATIONS</span>
</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
<a href="#" class="track-tile-item">
<div class="track-title">Embedded Silicon &amp; Hardware</div>
<div class="track-meta">[1 ARTICLE &bull; BUS PIRATE]</div>
<p class="track-desc">Physical chip extraction, UART/JTAG probing, SPI dump validation.</p>
</a>
<a href="#" class="track-tile-item">
<div class="track-title">Self-Hosted Infrastructure</div>
<div class="track-meta">[2 ARTICLES &bull; TRAEFIK &amp; WIREGUARD]</div>
<p class="track-desc">Automated edge routing, zero-trust overlay networks, VLAN firewalls.</p>
</a>
<a href="#" class="track-tile-item">
<div class="track-title">Low-Level Binary Exploitation</div>
<div class="track-meta">[1 ARTICLE &bull; GLIBC HEAP]</div>
<p class="track-desc">Memory allocator internals, tcache safe-linking mangling bypass.</p>
</a>
<a href="#" class="track-tile-item">
<div class="track-title">Zero-Allocation Tooling</div>
<div class="track-meta">[1 ARTICLE &bull; POSIX SH]</div>
<p class="track-desc">High-throughput logging, shell parser ASTs, minimalist runtimes.</p>
</a>
</div>
</section>
<!-- More Articles in Archive Tile -->
<section class="tile" aria-label="Additional Dispatches">
<div class="tile-header">
<div class="tile-title">
<span>// CTF &amp; SECURITY WIRE</span>
</div>
<span class="tile-meta">REPORTS</span>
</div>
<div style="display: flex; flex-direction: column; gap: 10px;">
<!-- Post 4: Heap Exploitation -->
<div class="compact-feed-item article-container" data-keywords="ctf binary exploitation heap glibc 2.35 memory security">
<span class="compact-date">2026-06-28 &bull; 15 min read</span>
<a href="#" class="compact-title">&gt; CTF Write-up: Heap Exploitation in glibc 2.35</a>
<p style="font-size: 0.8rem; color: var(--text-muted); line-height: 1.45;">
Deconstructing tcache safe-linking pointer mangling, bypass primitives, and House of Apple file structure hijacking.
</p>
<div style="font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; color: var(--text-muted); margin-top: 4px;">
[#ctf] [#binary-exploitation]
</div>
</div>
<!-- Post 5: Zero-Trust WireGuard -->
<div class="compact-feed-item article-container" data-keywords="networking wireguard security zerotrust opnsense vlans">
<span class="compact-date">2026-06-14 &bull; 10 min read</span>
<a href="#" class="compact-title">&gt; Zero-Trust Network Isolation with WireGuard &amp; OPNsense VLANs</a>
<p style="font-size: 0.8rem; color: var(--text-muted); line-height: 1.45;">
Designing strict zone-based firewall rules across isolated VLANs with point-to-point WireGuard cryptographic routing.
</p>
<div style="font-family: 'IBM Plex Mono', monospace; font-size: 0.72rem; color: var(--text-muted); margin-top: 4px;">
[#networking] [#wireguard]
</div>
</div>
</div>
</section>
</div>
</div>
<!-- Global Footer Tile (Matching Gateway 3 Exactly) -->
<footer class="footer-tile">
<div>River Rooks &bull; University of Alabama College of Engineering</div>
<nav class="footer-links-cluster" aria-label="Global Footer Links">
<a href="service-gateway-tiling.html" class="footer-btn">Gateway Lander</a>
<span class="pipe">|</span>
<a href="feed.xml" class="footer-btn">RSS</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-btn">Forgejo</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-btn">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="#" title="Fingerprint: 0x4A8E92F1C0B3D4E7" class="footer-btn">PGP</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-btn">Catalog ↗</a>
</nav>
</footer>
</main>
<script>
function filterArticles(query) {
const q = query.toLowerCase().trim();
const items = document.querySelectorAll('.article-container');
let matches = 0;
items.forEach(item => {
const text = (item.innerText + ' ' + (item.getAttribute('data-keywords') || '')).toLowerCase();
if (!q || text.includes(q)) {
item.style.display = '';
matches++;
} else {
item.style.display = 'none';
}
});
document.getElementById('matchStats').innerText = `${matches} dispatches visible`;
}
</script>
</body>
</html>
+15
View File
@@ -271,3 +271,18 @@ This matrix specifies which components and data entries from this canonical docu
| [`samples/sample-dashboard.html`](file:///home/venus/code/blog/samples/sample-dashboard.html) | Sample 8: 3-Tile Workspace | Center tile bio | Left tile services | Right tile archive | Center tile projects | Status bar | Vim command bar |
| [`samples/sample-command-center.html`](file:///home/venus/code/blog/samples/sample-command-center.html) | Sample 9: Tabbed Command Center | Tab 3 (Resume) | Tab 1 (Services) | Tab 2 (Blog Notes) | Tab 3 (Projects) | Tab 1 (Nodes) | Status dots |
| [`samples/sample-dual-pane-explorer.html`](file:///home/venus/code/blog/samples/sample-dual-pane-explorer.html) | Sample 10: Helix Tree Explorer | Sidebar links | Services subtree | Notes subtree | Modules subtree | Status bar | Sidebar companion |
| [`samples/blog-lander-stream.html`](file:///home/venus/code/blog/samples/blog-lander-stream.html) | Blog Lander 1: Minimalist Stream | Header & Bio | — | Posts 1–5 (Linear Feed & Topic Filter) | — | — | — |
| [`samples/blog-lander-terminal.html`](file:///home/venus/code/blog/samples/blog-lander-terminal.html) | Blog Lander 2: Terminal Directory Index | Header & Subtitle | — | Posts 1–5 (Dense Tabular Index & Code Inspection) | — | Terminal Status Bar | — |
| [`samples/blog-lander-cards.html`](file:///home/venus/code/blog/samples/blog-lander-cards.html) | Blog Lander 3: Curated Dispatch & Cards | Profile & Bio | — | Post 1 (Pinned Lead Card) + Posts 2–5 (Cards Grid) | — | — | — |
| [`samples/service-gateway-manifesto.html`](file:///home/venus/code/blog/samples/service-gateway-manifesto.html) | Gateway Lander 1: Manifesto & System Hub | Hero Manifesto & Bio | 6 Core/Public Services | Posts 1–3 Excerpts | — | Status banner | — |
| [`samples/service-gateway-terminal.html`](file:///home/venus/code/blog/samples/service-gateway-terminal.html) | Gateway Lander 2: Host Console & MOTD Router | MOTD Narrative & Specs | 6 Tabular Services | Posts 1–5 Log Lines | — | Hardware summary | — |
| [`samples/service-gateway-tiling.html`](file:///home/venus/code/blog/samples/service-gateway-tiling.html) | Gateway Lander 3: Tiling Workspace Hub | Hero Landing Tile | 5 Grid Services (Blog Core Tile) | Posts 1–4 Sublinks | — | Nodes 1–3 Live Telemetry | — |
| [`samples/blog-tiling-lead.html`](file:///home/venus/code/blog/samples/blog-tiling-lead.html) | Tiling Blog 1: Hero Dispatch & Dual Stream | Hero Tile & Topic Filter | — | Post 1 (Lead with Code) + Posts 2–5 (Feed) | — | Archive Telemetry | — |
| [`samples/blog-tiling-split.html`](file:///home/venus/code/blog/samples/blog-tiling-split.html) | Tiling Blog 2: Asymmetric 3-Tile Workspace | Hero Tile & Grep Bar | — | Posts 1–3 (Featured) + Posts 4–5 (Wire) | — | Curated Tracks | — |
| [`samples/blog-tiling-catalog.html`](file:///home/venus/code/blog/samples/blog-tiling-catalog.html) | Tiling Blog 3: Dual-Lead Grid & Inspection Feed | Hero Tile & Badges | — | Posts 1–2 (Dual Leads) + Posts 3–5 (Table) | — | Inline Note Inspector | — |
| [`samples/article-tiling-firmware.html`](file:///home/venus/code/blog/samples/article-tiling-firmware.html) | Article 1: IoT Firmware Extraction | Header & Meta | — | Post 1 (Full Markdown, Code, Tables, Alerts) | — | Winbond 25Q64 specs | — |
| [`samples/article-tiling-traefik.html`](file:///home/venus/code/blog/samples/article-tiling-traefik.html) | Article 2: HA Traefik Router | Header & Meta | — | Post 2 (Full Markdown, Compose YAML, ASCII Diagram) | — | pve-node02.lan specs | — |
| [`samples/article-tiling-heap.html`](file:///home/venus/code/blog/samples/article-tiling-heap.html) | Article 3: CTF Heap Exploitation | Header & Meta | — | Post 4 (Full Markdown, GDB session, C payload, Formula) | — | glibc 2.35 runtime | — |
+125 -2
View File
@@ -432,6 +432,42 @@
<button class="sample-btn" data-file="sample-dual-pane-explorer.html" onclick="loadSample('sample-dual-pane-explorer.html', this)">
<span>[0] Explorer</span>
</button>
<button class="sample-btn" data-file="blog-lander-stream.html" onclick="loadSample('blog-lander-stream.html', this)">
<span>[Lander 1] Stream</span>
</button>
<button class="sample-btn" data-file="blog-lander-terminal.html" onclick="loadSample('blog-lander-terminal.html', this)">
<span>[Lander 2] Terminal</span>
</button>
<button class="sample-btn" data-file="blog-lander-cards.html" onclick="loadSample('blog-lander-cards.html', this)">
<span>[Lander 3] Cards</span>
</button>
<button class="sample-btn" data-file="service-gateway-manifesto.html" onclick="loadSample('service-gateway-manifesto.html', this)">
<span>[Gateway 1] Manifesto</span>
</button>
<button class="sample-btn" data-file="service-gateway-terminal.html" onclick="loadSample('service-gateway-terminal.html', this)">
<span>[Gateway 2] Host Console</span>
</button>
<button class="sample-btn" data-file="service-gateway-tiling.html" onclick="loadSample('service-gateway-tiling.html', this)">
<span>[Gateway 3] Tiling Hub</span>
</button>
<button class="sample-btn" data-file="blog-tiling-lead.html" onclick="loadSample('blog-tiling-lead.html', this)">
<span>[Tiling Blog 1] Lead</span>
</button>
<button class="sample-btn" data-file="blog-tiling-split.html" onclick="loadSample('blog-tiling-split.html', this)">
<span>[Tiling Blog 2] Workspace</span>
</button>
<button class="sample-btn" data-file="blog-tiling-catalog.html" onclick="loadSample('blog-tiling-catalog.html', this)">
<span>[Tiling Blog 3] Dual-Lead</span>
</button>
<button class="sample-btn" data-file="article-tiling-firmware.html" onclick="loadSample('article-tiling-firmware.html', this)">
<span>[Article 1] Firmware</span>
</button>
<button class="sample-btn" data-file="article-tiling-traefik.html" onclick="loadSample('article-tiling-traefik.html', this)">
<span>[Article 2] Traefik</span>
</button>
<button class="sample-btn" data-file="article-tiling-heap.html" onclick="loadSample('article-tiling-heap.html', this)">
<span>[Article 3] Heap CTF</span>
</button>
<button class="sample-btn" data-file="canonical-sample-content.md" onclick="loadSample('canonical-sample-content.md', this)">
<span>[Dataset] Content Spec</span>
</button>
@@ -489,7 +525,7 @@
<section class="catalog-drawer" id="catalogDrawer" aria-label="Catalog Grid View">
<div style="display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px;">
<h2 style="font-family: 'IBM Plex Mono', monospace; font-size: 1.2rem; color: var(--text-primary);">Design Spec Samples Catalog</h2>
<span style="font-family: 'IBM Plex Mono', monospace; font-size: 0.8rem; color: var(--text-muted);">15 Artifacts Registered</span>
<span style="font-family: 'IBM Plex Mono', monospace; font-size: 0.8rem; color: var(--text-muted);">27 Artifacts Registered</span>
</div>
<div class="drawer-grid" id="drawerGrid"></div>
</section>
@@ -572,6 +608,90 @@
type: 'html',
desc: 'Split-screen file-tree explorer with interactive document viewer and sys-daemon mascot reacting to folder item selection.'
},
{
key: 'l1',
file: 'blog-lander-stream.html',
title: 'Blog Lander 1: Minimalist Stream',
type: 'html',
desc: 'Single-column 65ch linear reading stream with topic filter pills, canonical article abstracts, tag clusters, and PGP/RSS links.'
},
{
key: 'l2',
file: 'blog-lander-terminal.html',
title: 'Blog Lander 2: Terminal Directory Index',
type: 'html',
desc: 'High-density POSIX tabular directory index mimicking ls -la with real-time grep filter and inline code inspection drawer.'
},
{
key: 'l3',
file: 'blog-lander-cards.html',
title: 'Blog Lander 3: Curated Dispatch & Cards',
type: 'html',
desc: 'Structured blog lander with a pinned lead article card featuring code snippet preview, and a 2-column grid of recent technical dispatches.'
},
{
key: 'g1',
file: 'service-gateway-manifesto.html',
title: 'Gateway Lander 1: Manifesto & System Hub',
type: 'html',
desc: 'Gateway portal where the homepage manifesto is the central focus, routing visitors to homelab services (with the blog highlighted) and recent dispatches.'
},
{
key: 'g2',
file: 'service-gateway-terminal.html',
title: 'Gateway Lander 2: Host Console & MOTD Router',
type: 'html',
desc: 'Terminal-style MOTD gateway centering on system landing prose, an interactive service routing table featuring the blog, and live dispatches log.'
},
{
key: 'g3',
file: 'service-gateway-tiling.html',
title: 'Gateway Lander 3: Tiling Workspace Hub',
type: 'html',
desc: 'Tiling window manager aesthetic with a prominent hero landing tile, a dedicated core blog feature tile, homelab services grid, and node telemetry.'
},
{
key: 'tb1',
file: 'blog-tiling-lead.html',
title: 'Tiling Blog 1: Hero Dispatch & Dual Stream',
type: 'html',
desc: 'Gateway 3 tiling aesthetic applied to the blog, featuring a pinned lead write-up with code preview, research tracks, and full-width chronological feed.'
},
{
key: 'tb2',
file: 'blog-tiling-split.html',
title: 'Tiling Blog 2: Asymmetric 3-Tile Workspace',
type: 'html',
desc: 'Asymmetric 3-tile blog workspace with interactive live grep filter, embedded code blocks, curated tracks, and security wire.'
},
{
key: 'tb3',
file: 'blog-tiling-catalog.html',
title: 'Tiling Blog 3: Dual-Lead Grid & Inspection Feed',
type: 'html',
desc: 'Dual-lead side-by-side featured article tiles with code snippets, plus an archive feed table with an interactive inline note inspector.'
},
{
key: 'a1',
file: 'article-tiling-firmware.html',
title: 'Article 1: IoT Firmware Reverse Engineering',
type: 'html',
desc: 'Article reading template featuring left sidebar TOC, collapsible related dispatches drawer, SOP8 pinout matrix, flashrom code blocks with copy utility, alerts, and task lists.'
},
{
key: 'a2',
file: 'article-tiling-traefik.html',
title: 'Article 2: High-Availability Traefik Router',
type: 'html',
desc: 'Article reading template featuring right sidebar TOC, collapsible homelab series drawer, ASCII network routing diagram, YAML compose code blocks, and configuration matrix.'
},
{
key: 'a3',
file: 'article-tiling-heap.html',
title: 'Article 3: CTF Heap Exploitation in glibc 2.35',
type: 'html',
desc: 'Article reading template featuring left sidebar TOC, collapsible security drawer, safe-linking formula box, GDB stdout terminal blocks, C structs, and mitigation history table.'
},
{
key: 'd',
file: 'canonical-sample-content.md',
@@ -757,7 +877,10 @@
'7': 'public-services.html',
'8': 'sample-dashboard.html',
'9': 'sample-command-center.html',
'0': 'sample-dual-pane-explorer.html'
'0': 'sample-dual-pane-explorer.html',
'b': 'blog-lander-stream.html',
't': 'blog-lander-terminal.html',
'c': 'blog-lander-cards.html'
};
if (keyMap[e.key]) {
+587
View File
@@ -0,0 +1,587 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Node Gateway &amp; System Hub</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Card surface background */
--bg-elevated: #1c1f26; /* Hovered surface */
--border-color: #2b303b; /* Low-contrast 1px border */
--text-primary: #e0e6f0; /* Off-white prose */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Primary terminal accent */
--accent-green: #00e676; /* Operational indicator */
--accent-amber: #ffb700; /* Internal / protected warning */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.65;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 40px 20px 64px 20px;
}
.hub-container {
width: 100%;
max-width: 780px;
}
/* Top Breadcrumb & Status Bar */
.top-status-bar {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
padding-bottom: 12px;
margin-bottom: 28px;
border-bottom: 1px solid var(--border-color);
flex-wrap: wrap;
gap: 10px;
}
.node-identifier {
display: flex;
align-items: center;
gap: 8px;
}
.status-dot {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
flex-shrink: 0;
}
.status-pill {
color: var(--accent-green);
font-weight: 500;
}
/* Main Landing Narrative Section (Primary Focus) */
.landing-hero {
margin-bottom: 36px;
}
.author-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 2.2rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
margin-bottom: 6px;
}
.author-tagline {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.85rem;
color: var(--accent-cyan);
margin-bottom: 20px;
letter-spacing: 0.04em;
}
.manifesto-prose {
font-size: 1.02rem;
color: var(--text-primary);
line-height: 1.7;
margin-bottom: 20px;
}
.manifesto-prose p {
margin-bottom: 16px;
}
.callout-block {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-left: 3px solid var(--accent-cyan);
border-radius: 3px;
padding: 14px 18px;
font-size: 0.9rem;
color: var(--text-muted);
margin-bottom: 24px;
font-family: 'IBM Plex Mono', monospace;
line-height: 1.55;
}
.callout-block strong {
color: var(--text-primary);
}
/* 1px Solid Section Divider */
.section-divider {
border: 0;
border-top: 1px solid var(--border-color);
margin: 36px 0;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 16px;
}
.section-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.95rem;
font-weight: 700;
color: var(--text-primary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.section-meta {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.76rem;
color: var(--text-muted);
}
/* Direction to Services Router */
.services-router {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 40px;
}
.service-card {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 16px 20px;
display: flex;
flex-direction: column;
gap: 8px;
text-decoration: none;
color: inherit;
transition: none; /* Instant snap */
}
.service-card:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.service-card:focus {
outline: 2px solid var(--accent-cyan);
outline-offset: -1px;
}
/* Highlighted Blog Service Card */
.service-card.service-primary {
border-left: 3px solid var(--accent-cyan);
}
.service-top-row {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
}
.service-name-group {
display: flex;
align-items: center;
gap: 10px;
}
.service-name {
font-size: 1.05rem;
font-weight: 700;
color: var(--text-primary);
}
.service-card:hover .service-name {
color: var(--accent-cyan);
}
.service-badge {
font-size: 0.72rem;
padding: 2px 6px;
border-radius: 2px;
font-weight: 500;
}
.badge-blog {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
font-weight: 700;
}
.badge-public {
border: 1px solid var(--accent-green);
color: var(--accent-green);
}
.badge-internal {
border: 1px solid var(--accent-amber);
color: var(--accent-amber);
}
.service-endpoint {
color: var(--text-muted);
font-size: 0.76rem;
}
.service-desc {
font-size: 0.9rem;
color: var(--text-primary);
line-height: 1.5;
}
.service-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
margin-top: 4px;
}
/* Recent Articles Feed Section */
.articles-preview-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.article-item {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 14px 18px;
display: flex;
flex-direction: column;
gap: 6px;
}
.article-item:hover {
border-color: var(--accent-cyan);
}
.article-meta-row {
display: flex;
justify-content: space-between;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.75rem;
color: var(--text-muted);
}
.article-date {
color: var(--accent-cyan);
}
.article-heading {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.98rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
transition: none;
}
.article-heading:hover {
color: var(--accent-cyan);
}
.article-summary-text {
font-size: 0.88rem;
color: var(--text-primary);
line-height: 1.5;
}
/* Footer */
.hub-footer {
margin-top: 48px;
padding-top: 24px;
border-top: 1px solid var(--border-color);
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--text-muted);
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.footer-links {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.footer-link {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-link:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="hub-container">
<!-- Top Status Line -->
<header class="top-status-bar">
<div class="node-identifier">
<span class="status-dot"></span>
<span>node: gw-node01.lan [RACK-01 / ALABAMA]</span>
</div>
<div class="status-pill">[SYSTEMS OPERATIONAL • 10GbE]</div>
</header>
<!-- Main Landing Narrative (Core Focus) -->
<section class="landing-hero" aria-label="System Introduction & Landing Manifesto">
<h1 class="author-title">River Rooks</h1>
<div class="author-tagline">SYSTEMS ARCHITECTURE &bull; CYBERSECURITY &bull; SELF-HOSTED CLUSTER</div>
<div class="manifesto-prose">
<p>
Welcome to my personal computing node and technical gateway. I am an engineering student at the University of Alabama specializing in network forensics, bare-metal hypervisors, and security systems.
</p>
<p>
This server is not a commercial landing page or marketing platform. It is a sovereign, self-hosted operational command hub operating from a dedicated 10GbE home rack. All services below run on hardened local hypervisors (Proxmox VE, OPNsense, TrueNAS) routed through an automated Traefik reverse proxy with WireGuard zero-trust mesh peering.
</p>
<p>
Use this landing portal to navigate the ecosystem. The <strong>Technical Blog</strong> contains deep-dive research notes and CTF post-mortems. The <strong>Public Services</strong> are live, free-to-use developer and cryptographic utilities available to the public, while the <strong>Internal Infrastructure</strong> links route directly to cluster monitoring and orchestration consoles.
</p>
</div>
<div class="callout-block">
<strong>ROUTING NOTICE:</strong> External ingress is authenticated and monitored via OPNsense Suricata IDS/IPS. Public utilities are open for interactive use without registration.
</div>
</section>
<hr class="section-divider">
<!-- Direction to Services (With Blog as Primary Endpoint) -->
<section aria-label="Homelab Services Directory">
<div class="section-header">
<h2 class="section-title">Directory of Hosted Services &amp; Tools</h2>
<span class="section-meta">Select a destination &rarr;</span>
</div>
<div class="services-router">
<!-- Service 1: The Blog (Primary Service) -->
<a href="blog-lander-stream.html" class="service-card service-primary" title="Go to Technical Blog & Dispatches">
<div class="service-top-row">
<div class="service-name-group">
<span class="service-name">&gt; Technical Notes &amp; Engineering Blog</span>
<span class="service-badge badge-blog">[BLOG / DISPATCHES]</span>
</div>
<span class="service-endpoint">~/blog/notes/ &bull; rooks.dev/notes</span>
</div>
<p class="service-desc">
The core writing repository. Reverse-chronological feed of technical articles covering IoT SPI flash extraction, glibc 2.35 heap exploitation, high-availability Traefik routing, and POSIX shell engineering.
</p>
<div class="service-tags">
<span>[#ctf-writeups]</span>
<span>[#firmware-reversing]</span>
<span>[#traefik-docker]</span>
<span>[#posix-sh]</span>
<span style="color: var(--accent-cyan); font-weight: 500;">[OPEN BLOG FEED &rarr;]</span>
</div>
</a>
<!-- Service 2: Forgejo Git Server -->
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="service-card" title="Open Self-Hosted Git Forge">
<div class="service-top-row">
<div class="service-name-group">
<span class="service-name">Forgejo Git Server</span>
<span class="service-badge badge-public">[PUBLIC FORGE]</span>
</div>
<span class="service-endpoint">https://git.rooks.dev &bull; :2222/ssh</span>
</div>
<p class="service-desc">
Lightweight, independent Git forge hosting infrastructure-as-code playbooks, dotfiles, POSIX shell parser modules, and bare-metal kernel bootstrap code.
</p>
<div class="service-tags">
<span>[#iac]</span>
<span>[#ansible]</span>
<span>[#cortex-m]</span>
<span>[#git]</span>
</div>
</a>
<!-- Service 3: CyberChef Workbench -->
<a href="https://chef.rooks.dev" target="_blank" rel="noopener" class="service-card" title="Open CyberChef Workbench">
<div class="service-top-row">
<div class="service-name-group">
<span class="service-name">CyberChef Security Workbench</span>
<span class="service-badge badge-public">[PUBLIC TOOL]</span>
</div>
<span class="service-endpoint">https://chef.rooks.dev &bull; :443</span>
</div>
<p class="service-desc">
Client-side cryptographic Swiss Army knife for hex decoding, Base64 operations, XOR decryption, hashing algorithms, and CTF payload deconstruction.
</p>
<div class="service-tags">
<span>[#cryptography]</span>
<span>[#forensics]</span>
<span>[#ctf-toolkit]</span>
</div>
</a>
<!-- Service 4: Stirling PDF Studio -->
<a href="https://pdf.rooks.dev" target="_blank" rel="noopener" class="service-card" title="Open Stirling PDF Suite">
<div class="service-top-row">
<div class="service-name-group">
<span class="service-name">Stirling PDF Studio</span>
<span class="service-badge badge-public">[PUBLIC TOOL]</span>
</div>
<span class="service-endpoint">https://pdf.rooks.dev &bull; :443</span>
</div>
<p class="service-desc">
Locally hosted private document toolkit for OCR text extraction, PDF merging, splitting, sanitization, and password encryption without external cloud telemetry.
</p>
<div class="service-tags">
<span>[#pdf-tools]</span>
<span>[#ocr]</span>
<span>[#privacy-first]</span>
</div>
</a>
<!-- Service 5: Proxmox VE & TrueNAS Cluster Management -->
<a href="homelab.html" class="service-card" title="Inspect Homelab Infrastructure">
<div class="service-top-row">
<div class="service-name-group">
<span class="service-name">Homelab Cluster &amp; Storage</span>
<span class="service-badge badge-internal">[INTERNAL / HYPERVISOR]</span>
</div>
<span class="service-endpoint">https://pve.rooks.dev &bull; 48TB ZFS</span>
</div>
<p class="service-desc">
Hypervisor cluster with AMD Ryzen 9 5900X (12C/24T), 128GB ECC RAM, and TrueNAS SCALE RaidZ2 storage pool. View node topology and live resource utilization telemetry.
</p>
<div class="service-tags">
<span>[#proxmox-ve]</span>
<span>[#truenas]</span>
<span>[#10gbe-mesh]</span>
</div>
</a>
<!-- Service 6: Uptime Kuma System Telemetry -->
<a href="https://status.rooks.dev" target="_blank" rel="noopener" class="service-card" title="Open Status Dashboard">
<div class="service-top-row">
<div class="service-name-group">
<span class="service-name">Uptime Kuma Health Monitor</span>
<span class="service-badge badge-public">[STATUS / TELEMETRY]</span>
</div>
<span class="service-endpoint">https://status.rooks.dev &bull; :3001</span>
</div>
<p class="service-desc">
Continuous synthetic HTTP, TCP, and ICMP health checks broadcasting real-time cluster uptime, DNS resolution latencies, and service availability.
</p>
<div class="service-tags">
<span>[#uptime-100%]</span>
<span>[#telemetry]</span>
<span>[#prometheus]</span>
</div>
</a>
</div>
</section>
<hr class="section-divider">
<!-- Recent Technical Writing (Direct Excerpts from the Blog) -->
<section aria-label="Latest Blog Dispatches">
<div class="section-header">
<h2 class="section-title">Latest Technical Dispatches From the Blog</h2>
<a href="blog-lander-stream.html" style="color: var(--accent-cyan); font-family: 'IBM Plex Mono', monospace; font-size: 0.78rem; text-decoration: none;">View all 5 entries &rarr;</a>
</div>
<div class="articles-preview-list">
<article class="article-item">
<div class="article-meta-row">
<time datetime="2026-07-20" class="article-date">2026-07-20 &bull; 8 min read</time>
<span>[#firmware] [#hardware]</span>
</div>
<a href="blog-lander-stream.html" class="article-heading">&gt; Reverse Engineering IoT Firmware: SPI Flash Extraction</a>
<p class="article-summary-text">
Bypassing bootloader lockdowns by dumping physical SPI Flash memory from an 8-pin SOP chip using a Bus Pirate and flashrom.
</p>
</article>
<article class="article-item">
<div class="article-meta-row">
<time datetime="2026-07-15" class="article-date">2026-07-15 &bull; 12 min read</time>
<span>[#homelab] [#docker-traefik]</span>
</div>
<a href="blog-lander-stream.html" class="article-heading">&gt; Building a High-Availability Traefik Router with Docker Compose</a>
<p class="article-summary-text">
Architecting automated TLS wildcard certificate propagation, Docker provider label routing, zero-downtime container updates, and health checks.
</p>
</article>
<article class="article-item">
<div class="article-meta-row">
<time datetime="2026-07-10" class="article-date">2026-07-10 &bull; 6 min read</time>
<span>[#posix-sh] [#unix]</span>
</div>
<a href="blog-lander-stream.html" class="article-heading">&gt; POSIX sh vs. Bash: Portability in Modern Scripts</a>
<p class="article-summary-text">
Eliminating bashisms and writing bulletproof, zero-allocation bootstrapping scripts for minimal Alpine and embedded Linux environments.
</p>
</article>
</div>
</section>
<!-- Footer -->
<footer class="hub-footer">
<div>River Rooks &bull; Cluster gw-node01.lan</div>
<nav class="footer-links" aria-label="Portal Footer Navigation">
<a href="blog-lander-stream.html" class="footer-link">Blog</a>
<span class="pipe">|</span>
<a href="feed.xml" class="footer-link">RSS</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-link">Git Forge</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-link">Email</a>
<span class="pipe">|</span>
<a href="#" title="Fingerprint: 0x4A8E92F1C0B3D4E7" class="footer-link">PGP Key</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-link">Samples Catalog &rarr;</a>
</nav>
</footer>
</main>
</body>
</html>
+609
View File
@@ -0,0 +1,609 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Homelab Host Console &amp; Service Router</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Table & card surface */
--bg-elevated: #1c1f26; /* Selected row / elevated state */
--border-color: #2b303b; /* Low-contrast 1px border */
--text-primary: #e0e6f0; /* Light off-white prose */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Primary terminal accent */
--accent-green: #00e676; /* Operational status indicator */
--accent-amber: #ffb700; /* Internal / protected warning */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'IBM Plex Mono', monospace;
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 32px 16px 56px 16px;
}
.terminal-container {
width: 100%;
max-width: 920px;
}
/* Terminal Window Shell */
.terminal-shell {
border: 1px solid var(--border-color);
border-radius: 4px;
background-color: var(--bg-surface);
overflow: hidden;
}
.shell-titlebar {
background-color: var(--bg-canvas);
border-bottom: 1px solid var(--border-color);
padding: 8px 16px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.78rem;
color: var(--text-muted);
}
.window-dots {
display: flex;
align-items: center;
gap: 6px;
}
.dot {
width: 8px;
height: 8px;
background-color: var(--border-color);
display: inline-block;
}
.shell-body {
padding: 24px;
}
/* MOTD / Welcome Landing Narrative (Primary Focus) */
.motd-banner {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 20px;
margin-bottom: 28px;
}
.motd-header {
font-size: 0.8rem;
color: var(--accent-cyan);
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 8px;
}
.motd-title {
font-size: 1.4rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 6px;
letter-spacing: -0.02em;
}
.motd-subtitle {
font-size: 0.82rem;
color: var(--text-muted);
margin-bottom: 14px;
}
.motd-prose {
font-family: 'Inter', sans-serif;
font-size: 0.94rem;
color: var(--text-primary);
line-height: 1.65;
}
.motd-prose p {
margin-bottom: 12px;
}
.motd-prose p:last-child {
margin-bottom: 0;
}
.motd-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 10px;
margin-top: 16px;
padding-top: 14px;
border-top: 1px solid var(--border-color);
font-size: 0.76rem;
color: var(--text-muted);
}
.stat-val {
color: var(--text-primary);
font-weight: 700;
}
/* Command Prompt Separator */
.cmd-bar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 16px;
font-size: 0.86rem;
}
.cmd-prompt {
color: var(--accent-cyan);
font-weight: 700;
}
.cmd-text {
color: var(--text-primary);
}
/* Services Routing Table */
.services-table-wrapper {
width: 100%;
overflow-x: auto;
margin-bottom: 36px;
}
.services-table {
width: 100%;
border-collapse: collapse;
font-size: 0.82rem;
text-align: left;
}
.services-table th {
padding: 8px 12px;
background-color: var(--bg-canvas);
border-bottom: 1px solid var(--border-color);
color: var(--text-muted);
font-weight: 500;
white-space: nowrap;
}
.services-table td {
padding: 10px 12px;
border-bottom: 1px solid var(--border-color);
vertical-align: middle;
white-space: nowrap;
}
.service-row {
transition: none; /* Instant snap */
}
.service-row:hover {
background-color: var(--bg-elevated);
}
.service-row.highlight-blog {
border-left: 2px solid var(--accent-cyan);
background-color: rgba(0, 240, 255, 0.03);
}
.service-link {
color: var(--text-primary);
text-decoration: none;
font-weight: 700;
display: inline-flex;
align-items: center;
gap: 6px;
}
.service-row:hover .service-link {
color: var(--accent-cyan);
}
.badge-status {
font-size: 0.72rem;
padding: 1px 6px;
border-radius: 2px;
}
.badge-online {
border: 1px solid var(--accent-green);
color: var(--accent-green);
}
.badge-blog-highlight {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
font-weight: 700;
border: 1px solid var(--accent-cyan);
}
.badge-protected {
border: 1px solid var(--accent-amber);
color: var(--accent-amber);
}
.action-btn {
color: var(--accent-cyan);
text-decoration: none;
font-size: 0.76rem;
padding: 2px 6px;
border: 1px solid transparent;
display: inline-block;
transition: none;
}
.service-row:hover .action-btn {
border-color: var(--accent-cyan);
}
/* Articles Stream Section */
.articles-section {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 20px;
}
.articles-header {
display: flex;
justify-content: space-between;
align-items: baseline;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
margin-bottom: 16px;
flex-wrap: wrap;
gap: 8px;
}
.articles-title {
font-size: 0.95rem;
font-weight: 700;
color: var(--text-primary);
}
.articles-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.article-row {
display: flex;
justify-content: space-between;
align-items: baseline;
padding: 6px 0;
border-bottom: 1px solid rgba(43, 48, 59, 0.4);
flex-wrap: wrap;
gap: 8px;
}
.article-row:last-child {
border-bottom: none;
}
.article-row-left {
display: flex;
align-items: baseline;
gap: 12px;
}
.article-date-mono {
color: var(--accent-cyan);
font-size: 0.78rem;
}
.article-title-click {
color: var(--text-primary);
text-decoration: none;
font-weight: 500;
font-size: 0.88rem;
}
.article-title-click:hover {
color: var(--accent-cyan);
text-decoration: underline;
}
.article-tags-mono {
color: var(--text-muted);
font-size: 0.74rem;
}
/* Status Bar Footer */
.shell-footer {
background-color: var(--bg-canvas);
border-top: 1px solid var(--border-color);
padding: 10px 18px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.75rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 8px;
}
.footer-nav {
display: flex;
gap: 10px;
align-items: center;
}
.footer-link-item {
color: var(--text-primary);
text-decoration: none;
padding: 1px 4px;
}
.footer-link-item:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="terminal-container">
<div class="terminal-shell">
<!-- Shell Window Bar -->
<div class="shell-titlebar">
<div class="window-dots">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
<span style="color: var(--text-primary); font-weight: 700; margin-left: 6px;">dev-blog@system: ~/gateway</span>
</div>
<div>SESSION: TTY1 &bull; 10GbE TRUNK &bull; UPTIME: 99.98%</div>
</div>
<div class="shell-body">
<!-- MOTD / Landing Narrative (Main Focus of Homepage) -->
<section class="motd-banner" aria-label="System Message Of The Day and Landing Introduction">
<div class="motd-header">
<span>/etc/motd &bull; BROADCAST MESSAGE</span>
<span>NODE: gw-node01.lan [RACK-01]</span>
</div>
<h1 class="motd-title">River Rooks // Homelab Gateway &amp; Service Router</h1>
<div class="motd-subtitle">Systems Architecture &bull; Network Forensics &bull; Bare-Metal Infrastructure</div>
<div class="motd-prose">
<p>
Welcome to the entrypoint for my self-hosted environment. I am a cybersecurity and systems student at the University of Alabama College of Engineering. This portal acts as the root router for both my external public services and internal virtualization infrastructure.
</p>
<p>
Rather than relying on commercial cloud silos, every application listed below runs directly on physical hardware housed in my home lab. Network traffic is segmented across dedicated VLANs (Management, Server DMZ, IoT) and shielded behind an OPNsense firewall with Suricata intrusion prevention and an automated Traefik reverse proxy handling Let's Encrypt TLS wildcard certificates.
</p>
<p>
<strong>Looking for technical writing?</strong> The <a href="blog-lander-terminal.html" style="color: var(--accent-cyan); text-decoration: underline;">Technical Notes &amp; Blog</a> service is where I document firmware reverse engineering, CTF binary exploitation, and zero-trust routing architectures. All public utilities (CyberChef, Stirling PDF, Git Forge) are open and ready for immediate use.
</p>
</div>
<div class="motd-stats-grid">
<div>GATEWAY: <span class="stat-val">OPNsense 24.7 (Intel N100)</span></div>
<div>COMPUTE: <span class="stat-val">Proxmox VE 8.2 (AMD 5900X)</span></div>
<div>STORAGE: <span class="stat-val">TrueNAS SCALE (48TB ZFS)</span></div>
<div>SECURITY: <span class="stat-val">WireGuard Full-Mesh</span></div>
</div>
</section>
<!-- Command Execution Bar -->
<div class="cmd-bar">
<span class="cmd-prompt">dev-blog@system:~$</span>
<span class="cmd-text">route-service --list-all --highlight=blog</span>
</div>
<!-- Directory of Hosted Services (Routing) -->
<div class="services-table-wrapper">
<table class="services-table" aria-label="Homelab Services Routing Table">
<thead>
<tr>
<th scope="col">SERVICE IDENTIFIER</th>
<th scope="col">ROUTING DOMAIN / PATH</th>
<th scope="col">ACCESS LEVEL</th>
<th scope="col">DESCRIPTION</th>
<th scope="col">DISPATCH</th>
</tr>
</thead>
<tbody>
<!-- Blog Service (Highlighted) -->
<tr class="service-row highlight-blog">
<td>
<a href="blog-lander-terminal.html" class="service-link">
<span>&gt; Technical Notes &amp; Blog</span>
</a>
</td>
<td style="color: var(--accent-cyan);">~/notes/ &bull; rooks.dev/notes</td>
<td><span class="badge-status badge-blog-highlight">[FEATURED BLOG]</span></td>
<td style="color: var(--text-primary); font-family: 'Inter', sans-serif;">
Technical writing on firmware reversing, heap exploitation, and networking.
</td>
<td><a href="blog-lander-terminal.html" class="action-btn">[OPEN BLOG &rarr;]</a></td>
</tr>
<!-- Forgejo Git -->
<tr class="service-row">
<td>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="service-link">
<span>Forgejo Git Forge</span>
</a>
</td>
<td style="color: var(--text-muted);">git.rooks.dev:2222</td>
<td><span class="badge-status badge-online">[PUBLIC OPEN]</span></td>
<td style="color: var(--text-primary); font-family: 'Inter', sans-serif;">
Self-hosted repositories: Ansible IaC, dotfiles, and POSIX parser modules.
</td>
<td><a href="https://git.rooks.dev" target="_blank" rel="noopener" class="action-btn">[VISIT FORGE]</a></td>
</tr>
<!-- CyberChef -->
<tr class="service-row">
<td>
<a href="https://chef.rooks.dev" target="_blank" rel="noopener" class="service-link">
<span>CyberChef Suite</span>
</a>
</td>
<td style="color: var(--text-muted);">chef.rooks.dev:443</td>
<td><span class="badge-status badge-online">[PUBLIC TOOL]</span></td>
<td style="color: var(--text-primary); font-family: 'Inter', sans-serif;">
Client-side decoding, hex analysis, hashing, and CTF payload processing.
</td>
<td><a href="https://chef.rooks.dev" target="_blank" rel="noopener" class="action-btn">[OPEN CHEF]</a></td>
</tr>
<!-- Stirling PDF -->
<tr class="service-row">
<td>
<a href="https://pdf.rooks.dev" target="_blank" rel="noopener" class="service-link">
<span>Stirling PDF Studio</span>
</a>
</td>
<td style="color: var(--text-muted);">pdf.rooks.dev:443</td>
<td><span class="badge-status badge-online">[PUBLIC TOOL]</span></td>
<td style="color: var(--text-primary); font-family: 'Inter', sans-serif;">
Local document manipulation: OCR, PDF merge, split, and sanitize.
</td>
<td><a href="https://pdf.rooks.dev" target="_blank" rel="noopener" class="action-btn">[LAUNCH PDF]</a></td>
</tr>
<!-- Uptime Kuma -->
<tr class="service-row">
<td>
<a href="https://status.rooks.dev" target="_blank" rel="noopener" class="service-link">
<span>Uptime Kuma Telemetry</span>
</a>
</td>
<td style="color: var(--text-muted);">status.rooks.dev:3001</td>
<td><span class="badge-status badge-online">[PUBLIC METRICS]</span></td>
<td style="color: var(--text-primary); font-family: 'Inter', sans-serif;">
Live latency metrics, cluster ping monitors, and real-time SLA status.
</td>
<td><a href="https://status.rooks.dev" target="_blank" rel="noopener" class="action-btn">[VIEW STATS]</a></td>
</tr>
<!-- Proxmox VE -->
<tr class="service-row">
<td>
<a href="homelab.html" class="service-link">
<span>Proxmox Hypervisor</span>
</a>
</td>
<td style="color: var(--text-muted);">pve.rooks.dev:8006</td>
<td><span class="badge-status badge-protected">[INTERNAL AUTH]</span></td>
<td style="color: var(--text-primary); font-family: 'Inter', sans-serif;">
KVM VMs and LXC container cluster on AMD Ryzen 9 5900X (12C/24T).
</td>
<td><a href="homelab.html" class="action-btn">[CLUSTER VIEW]</a></td>
</tr>
</tbody>
</table>
</div>
<!-- Command Execution Bar for Articles -->
<div class="cmd-bar">
<span class="cmd-prompt">dev-blog@system:~$</span>
<span class="cmd-text">tail -n 5 ~/notes/dispatches.log</span>
</div>
<!-- Articles Digest Section -->
<section class="articles-section" aria-label="Articles Feed">
<div class="articles-header">
<div class="articles-title">~/notes/dispatches.log &bull; RECENT TECHNICAL WRITING</div>
<a href="blog-lander-terminal.html" style="color: var(--accent-cyan); text-decoration: none; font-size: 0.78rem;">Full Archive &rarr;</a>
</div>
<div class="articles-list">
<div class="article-row">
<div class="article-row-left">
<span class="article-date-mono">2026-07-20</span>
<a href="blog-lander-terminal.html" class="article-title-click">Reverse Engineering IoT Firmware: SPI Flash Extraction</a>
</div>
<span class="article-tags-mono">[#firmware] [#hardware] &bull; 8m</span>
</div>
<div class="article-row">
<div class="article-row-left">
<span class="article-date-mono">2026-07-15</span>
<a href="blog-lander-terminal.html" class="article-title-click">Building a High-Availability Traefik Router with Docker Compose</a>
</div>
<span class="article-tags-mono">[#homelab] [#docker-traefik] &bull; 12m</span>
</div>
<div class="article-row">
<div class="article-row-left">
<span class="article-date-mono">2026-07-10</span>
<a href="blog-lander-terminal.html" class="article-title-click">POSIX sh vs. Bash: Portability in Modern Scripts</a>
</div>
<span class="article-tags-mono">[#posix-sh] [#unix] &bull; 6m</span>
</div>
<div class="article-row">
<div class="article-row-left">
<span class="article-date-mono">2026-06-28</span>
<a href="blog-lander-terminal.html" class="article-title-click">CTF Write-up: Heap Exploitation in glibc 2.35</a>
</div>
<span class="article-tags-mono">[#ctf] [#binary-exploitation] &bull; 15m</span>
</div>
<div class="article-row">
<div class="article-row-left">
<span class="article-date-mono">2026-06-14</span>
<a href="blog-lander-terminal.html" class="article-title-click">Zero-Trust Network Isolation with WireGuard &amp; OPNsense VLANs</a>
</div>
<span class="article-tags-mono">[#networking] [#wireguard] &bull; 10m</span>
</div>
</div>
</section>
</div>
<!-- Footer -->
<footer class="shell-footer">
<div>STATUS: 0 (OK) &bull; TLS: ACME WILDCARD &bull; NODE RACK-01</div>
<nav class="footer-nav" aria-label="Terminal Navigation Footer">
<a href="blog-lander-terminal.html" class="footer-link-item">Blog Lander</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-link-item">Forgejo Git</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-link-item">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-link-item">Catalog ↗</a>
</nav>
</footer>
</div>
</main>
</body>
</html>
+592
View File
@@ -0,0 +1,592 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>River Rooks | Homelab Gateway (Tiling Workspace)</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,700;1,400&family=Inter:wght@400;500;700&family=JetBrains+Mono:ital,wght@0,400;0,500;1,400&display=swap');
:root {
--bg-canvas: #0d0f12; /* Deep slate/charcoal canvas */
--bg-surface: #121417; /* Tile surface */
--bg-elevated: #1c1f26; /* Hovered tile */
--border-color: #2b303b; /* Crisp 1px border */
--text-primary: #e0e6f0; /* Prose off-white */
--text-muted: #8a95a5; /* Monospace metadata */
--accent-cyan: #00f0ff; /* Primary active accent */
--accent-green: #00e676; /* Online indicator */
--accent-amber: #ffb700; /* Warning / internal indicator */
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg-canvas);
color: var(--text-primary);
font-family: 'Inter', sans-serif;
line-height: 1.6;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 24px 16px 48px 16px;
}
.tiling-layout {
width: 100%;
max-width: 1120px;
display: flex;
flex-direction: column;
gap: 16px;
}
/* Top Global Status Header Bar */
.global-bar {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 10px 18px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
flex-wrap: wrap;
gap: 10px;
}
.brand-group {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-primary);
font-weight: 700;
}
.status-indicator {
width: 8px;
height: 8px;
background-color: var(--accent-green);
display: inline-block;
}
.bar-nav {
display: flex;
gap: 14px;
align-items: center;
}
.bar-link {
color: var(--text-muted);
text-decoration: none;
transition: none; /* Instant snap */
}
.bar-link:hover {
color: var(--accent-cyan);
}
/* Primary Hero Landing Tile (Main Focus of Homepage) */
.hero-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-left: 3px solid var(--accent-cyan);
border-radius: 4px;
padding: 28px 32px;
display: flex;
flex-direction: column;
gap: 16px;
}
.hero-pretitle {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--accent-cyan);
letter-spacing: 0.06em;
text-transform: uppercase;
}
.hero-main-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 2.2rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.02em;
line-height: 1.2;
}
.hero-landing-text {
font-size: 1.02rem;
color: var(--text-primary);
line-height: 1.7;
max-width: 80ch;
}
.hero-landing-text p {
margin-bottom: 14px;
}
.hero-landing-text p:last-child {
margin-bottom: 0;
}
.hero-directive-box {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 14px 18px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.84rem;
color: var(--text-muted);
line-height: 1.6;
}
.hero-directive-box strong {
color: var(--text-primary);
}
/* Tiling Grid Section: Split Columns */
.split-grid {
display: grid;
grid-template-columns: 1.15fr 0.85fr;
gap: 16px;
}
@media (max-width: 880px) {
.split-grid {
grid-template-columns: 1fr;
}
}
/* Tile Container Style */
.tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 20px;
display: flex;
flex-direction: column;
gap: 14px;
}
.tile-header {
display: flex;
justify-content: space-between;
align-items: baseline;
border-bottom: 1px solid var(--border-color);
padding-bottom: 10px;
}
.tile-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.95rem;
font-weight: 700;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 8px;
}
.tile-meta {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.74rem;
color: var(--text-muted);
}
/* Featured Service Tile: The Blog */
.blog-service-feature {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 16px;
display: flex;
flex-direction: column;
gap: 10px;
}
.blog-feature-top {
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
}
.badge-core {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
padding: 2px 6px;
font-weight: 700;
border-radius: 2px;
}
.blog-feature-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 1.15rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
display: inline-block;
transition: none;
}
.blog-feature-title:hover {
color: var(--accent-cyan);
}
.blog-feature-desc {
font-size: 0.9rem;
color: var(--text-primary);
line-height: 1.5;
}
.blog-sublinks {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 4px;
border-top: 1px solid var(--border-color);
padding-top: 10px;
}
.blog-sublink-item {
display: flex;
justify-content: space-between;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.8rem;
color: var(--text-primary);
text-decoration: none;
padding: 4px 6px;
border-radius: 2px;
transition: none;
}
.blog-sublink-item:hover {
background-color: var(--bg-elevated);
color: var(--accent-cyan);
}
/* Homelab Services Grid */
.services-mini-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
}
@media (max-width: 540px) {
.services-mini-grid {
grid-template-columns: 1fr;
}
}
.service-tile-item {
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
padding: 12px;
text-decoration: none;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 6px;
transition: none;
}
.service-tile-item:hover {
border-color: var(--accent-cyan);
background-color: var(--bg-elevated);
}
.service-item-title {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.88rem;
font-weight: 700;
color: var(--text-primary);
}
.service-tile-item:hover .service-item-title {
color: var(--accent-cyan);
}
.service-item-tag {
font-family: 'IBM Plex Mono', monospace;
font-size: 0.7rem;
color: var(--text-muted);
}
.service-item-desc {
font-size: 0.8rem;
color: var(--text-muted);
line-height: 1.4;
}
/* Infrastructure Nodes Telemetry Tile */
.node-stat-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
background-color: var(--bg-canvas);
border: 1px solid var(--border-color);
border-radius: 3px;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
}
.node-name {
color: var(--text-primary);
font-weight: 700;
}
.node-load {
color: var(--accent-green);
}
/* Bottom Global Footer Tile */
.footer-tile {
background-color: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 12px 20px;
display: flex;
justify-content: space-between;
align-items: center;
font-family: 'IBM Plex Mono', monospace;
font-size: 0.78rem;
color: var(--text-muted);
flex-wrap: wrap;
gap: 10px;
}
.footer-links-cluster {
display: flex;
gap: 12px;
align-items: center;
}
.footer-btn {
color: var(--text-primary);
text-decoration: none;
padding: 2px 4px;
transition: none;
}
.footer-btn:hover {
background-color: var(--accent-cyan);
color: var(--bg-canvas);
}
.pipe {
color: var(--border-color);
}
</style>
</head>
<body>
<main class="tiling-layout">
<!-- Top Status Bar -->
<header class="global-bar">
<div class="brand-group">
<span class="status-indicator"></span>
<span>RIVER ROOKS // NODE GATEWAY</span>
</div>
<nav class="bar-nav" aria-label="Quick Nav">
<a href="blog-lander-cards.html" class="bar-link">&gt; Blog &amp; Notes</a>
<a href="homelab.html" class="bar-link">Infrastructure</a>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="bar-link">Git Forge</a>
<a href="index.html" class="bar-link">Catalog ↗</a>
</nav>
</header>
<!-- Main Hero Landing Tile (Central Focus of Homepage) -->
<section class="hero-tile" aria-label="Homepage Mission & Landing Declaration">
<div class="hero-pretitle">SYSTEM DECLARATION &bull; RACK-01 NODE GATEWAY</div>
<h1 class="hero-main-title">Engineering Sovereign Infrastructure &amp; Technical Notes</h1>
<div class="hero-landing-text">
<p>
I am River Rooks, a cybersecurity engineering student at the University of Alabama. My work centers on network forensics, self-hosted system architecture, low-level firmware exploitation, and zero-allocation Unix utilities.
</p>
<p>
This website functions as the unified frontend for my physical homelab cluster. Rather than hosting projects on opaque third-party clouds, everything here runs on bare metal in my personal server rack—orchestrated through Proxmox VE, partitioned across isolated VLANs via OPNsense, and protected by point-to-point WireGuard cryptographic meshes.
</p>
<p>
<strong>Navigation Guide:</strong> If you are here to read technical research, the <strong>Engineering Blog</strong> is linked below with deep write-ups on firmware dumping, heap exploitation, and container routing. If you are here to use tools, the <strong>Public Services</strong> provide locally hosted instances of CyberChef, Stirling PDF, and Git.
</p>
</div>
<div class="hero-directive-box">
<strong>PRIMARY DESTINATIONS:</strong>
<span style="color: var(--accent-cyan); margin-left: 8px;">[1] Technical Blog (5 Articles)</span> &bull;
<span>[2] Forgejo Git Repos</span> &bull;
<span>[3] Public Cryptographic &amp; PDF Tools</span> &bull;
<span>[4] Cluster Telemetry</span>
</div>
</section>
<!-- Split Tiling Columns -->
<div class="split-grid">
<!-- Left Column: Featured Service (Blog) + Recent Articles -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<!-- Featured Service Tile: The Blog -->
<section class="tile" aria-label="Featured Service: Technical Blog">
<div class="tile-header">
<div class="tile-title">
<span>// PRIMARY SERVICE: THE TECHNICAL BLOG</span>
</div>
<span class="tile-meta">~/notes/ &bull; 5 ARTICLES</span>
</div>
<div class="blog-service-feature">
<div class="blog-feature-top">
<span class="badge-core">[CORE PUBLICATION SERVICE]</span>
<span style="color: var(--text-muted);">2026-07-20 LAST SYNC</span>
</div>
<a href="blog-lander-cards.html" class="blog-feature-title">&gt; River Rooks Technical Notes &amp; Research</a>
<p class="blog-feature-desc">
In-depth security post-mortems, CTF write-ups, shell portability benchmarks, and bare-metal homelab architecture guides.
</p>
<div class="blog-sublinks">
<a href="blog-lander-cards.html" class="blog-sublink-item">
<span>1. Reverse Engineering IoT Firmware (SPI Flash)</span>
<span style="color: var(--accent-cyan);">[8 min] &rarr;</span>
</a>
<a href="blog-lander-cards.html" class="blog-sublink-item">
<span>2. High-Availability Traefik with Docker Compose</span>
<span style="color: var(--accent-cyan);">[12 min] &rarr;</span>
</a>
<a href="blog-lander-cards.html" class="blog-sublink-item">
<span>3. POSIX sh vs. Bash: Portability in Modern Scripts</span>
<span style="color: var(--accent-cyan);">[6 min] &rarr;</span>
</a>
<a href="blog-lander-cards.html" class="blog-sublink-item">
<span>4. CTF Write-up: Heap Exploitation in glibc 2.35</span>
<span style="color: var(--accent-cyan);">[15 min] &rarr;</span>
</a>
</div>
</div>
</section>
</div>
<!-- Right Column: Homelab Hosted Services & Node Telemetry -->
<div style="display: flex; flex-direction: column; gap: 16px;">
<!-- Hosted Services Tile -->
<section class="tile" aria-label="Homelab Services Directory">
<div class="tile-header">
<div class="tile-title">
<span>// HOMELAB SERVICES ROUTER</span>
</div>
<span class="tile-meta">4 PUBLIC TOOLS</span>
</div>
<div class="services-mini-grid">
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="service-tile-item">
<div>
<div class="service-item-title">Forgejo Git</div>
<div class="service-item-tag">git.rooks.dev:2222</div>
</div>
<p class="service-item-desc">Self-hosted code repositories &amp; dotfiles.</p>
</a>
<a href="https://chef.rooks.dev" target="_blank" rel="noopener" class="service-tile-item">
<div>
<div class="service-item-title">CyberChef</div>
<div class="service-item-tag">chef.rooks.dev:443</div>
</div>
<p class="service-item-desc">Hex decoding, cryptography &amp; forensics.</p>
</a>
<a href="https://pdf.rooks.dev" target="_blank" rel="noopener" class="service-tile-item">
<div>
<div class="service-item-title">Stirling PDF</div>
<div class="service-item-tag">pdf.rooks.dev:443</div>
</div>
<p class="service-item-desc">Private document OCR and manipulation.</p>
</a>
<a href="https://status.rooks.dev" target="_blank" rel="noopener" class="service-tile-item">
<div>
<div class="service-item-title">Uptime Kuma</div>
<div class="service-item-tag">status.rooks.dev:3001</div>
</div>
<p class="service-item-desc">Live synthetic latency &amp; uptime telemetry.</p>
</a>
</div>
</section>
<!-- Bare-Metal Node Telemetry Tile -->
<section class="tile" aria-label="Physical Hardware Nodes">
<div class="tile-header">
<div class="tile-title">
<span>// HARDWARE NODES (RACK-01)</span>
</div>
<span class="tile-meta">10GbE SFP+</span>
</div>
<div style="display: flex; flex-direction: column; gap: 8px;">
<div class="node-stat-row">
<div>
<span class="node-name">gw-node01.lan</span>
<span style="color: var(--text-muted); font-size: 0.72rem; margin-left: 6px;">OPNsense 24.7</span>
</div>
<span class="node-load">CPU: 18% | RAM: 32%</span>
</div>
<div class="node-stat-row">
<div>
<span class="node-name">pve-node02.lan</span>
<span style="color: var(--text-muted); font-size: 0.72rem; margin-left: 6px;">Proxmox VE (AMD 5900X)</span>
</div>
<span class="node-load">CPU: 42% | RAM: 68%</span>
</div>
<div class="node-stat-row">
<div>
<span class="node-name">nas-node03.lan</span>
<span style="color: var(--text-muted); font-size: 0.72rem; margin-left: 6px;">TrueNAS (48TB RaidZ2)</span>
</div>
<span class="node-load">CPU: 14% | RAM: 82%</span>
</div>
</div>
</section>
</div>
</div>
<!-- Global Footer Bar -->
<footer class="footer-tile">
<div>River Rooks &bull; University of Alabama College of Engineering</div>
<nav class="footer-links-cluster" aria-label="Global Footer Links">
<a href="blog-lander-cards.html" class="footer-btn">Blog Lander</a>
<span class="pipe">|</span>
<a href="feed.xml" class="footer-btn">RSS</a>
<span class="pipe">|</span>
<a href="https://git.rooks.dev" target="_blank" rel="noopener" class="footer-btn">Forgejo</a>
<span class="pipe">|</span>
<a href="mailto:river@rooks.dev" class="footer-btn">river@rooks.dev</a>
<span class="pipe">|</span>
<a href="#" title="Fingerprint: 0x4A8E92F1C0B3D4E7" class="footer-btn">PGP</a>
<span class="pipe">|</span>
<a href="index.html" class="footer-btn">Catalog ↗</a>
</nav>
</footer>
</main>
</body>
</html>