23 Commits
Author SHA1 Message Date
venus 2bf5e83964 updated sample compose file to reflect zero downtime deployment on the actual compose file 2026-09-15 00:33:58 -05:00
venus d6c5ed1d3b feat: zero downtime deployment script unlocked 2026-09-15 00:31:56 -05:00
venus 528c302c74 updated docker update script with a bit more verbosity 2026-09-14 23:33:55 -05:00
venus 66c4e89732 feat: script to rebuild docker from latest image 2026-09-14 01:35:30 -05:00
venus 60f77838a0 updated server side infra 2026-09-13 21:28:18 -05:00
venus 04a39ca852 fix(healthcheck): correct arrow syntax and add unit tests 2026-09-13 15:09:11 -05:00
venus eddbf5470e updated healthcheck with a dedicated function 2026-09-13 14:38:49 -05:00
venus acd47b80aa test(docker): remove mandatory healthcheck requirement from compose test 2026-09-13 02:38:29 -05:00
venus 900014f506 fixed a failing test case on the server 2026-09-13 02:35:16 -05:00
venus 737a6e3e51 updated gitignore 2026-09-13 02:15:48 -05:00
venus 2423546ee6 updated gitignore 2026-09-13 02:11:40 -05:00
venus d55297f1dd fixed bug in compose file 2026-09-13 02:09:45 -05:00
venus d3aefa343e feat(docker): unify dynamic PORT across Dockerfile, compose, and dev server 2026-09-13 02:04:37 -05:00
venus 78b1e0b485 feat(docker): build and serve with Node and npm instead of Nginx 2026-09-13 01:59:49 -05:00
venus e733f479dd rewrote boilerplate site.js files 2026-09-13 01:50:38 -05:00
venus 1079b956d3 docs(rules): enforce User Content Sovereignty and remove specific content bias from guidelines and skills 2026-09-13 01:33:30 -05:00
venus 125b3e8839 fix(nav): update gateway navigation label from Gateway 3 to Gateway 2026-09-13 01:29:47 -05:00
venus cedd8194f6 feat: replace Forgejo references with Gitea and generalize content with Lorem Ipsum templates 2026-09-13 01:27:37 -05:00
venus e4601dbbcb feat: adopt strict TDD framework and add adversarial test suite
- Update AGENTS.md and .agents/rules/blog-system.md with strict TDD mandate
- Extract Eleventy custom filters into modular src/_filters/index.js for unit testing
- Add npm test script using Node.js native test runner (node:test)
- Create test/site-config.test.js for environment validation and fatal error assertions
- Create test/filters.test.js for adversarial filter edge cases (dates, limits, reading time, headings)
- Create test/frontmatter-schema.test.js enforcing post metadata contracts
- Create test/design-rules.test.js enforcing strict anti-patterns (no glassmorphism, no shadows, max 4px radius)
- Create test/build-integrity.test.js checking static output, zero 404 links, and TOC anchor targets
2026-09-13 01:16:20 -05:00
venus fc6c17b541 feat: throw explicit error when SITE_DOMAIN is unset
- Throw fatal configuration error in src/_data/site.js if SITE_DOMAIN is missing
- Require SITE_DOMAIN in docker-compose.yml without default fallback
- Require SITE_DOMAIN ARG in Dockerfile with no default fallback
2026-09-13 01:11:28 -05:00
venus 1491d3fa5c feat: make SITE_URL and SITE_DOMAIN dynamic via .env and docker compose
- Create .env and .env.example with SITE_DOMAIN=riverrooks.dev and SITE_URL=https://riverrooks.dev
- Migrate src/_data/site.json to src/_data/site.js reading environment variables dynamically
- Update subdomains across Forgejo, CyberChef, Stirling PDF, and Uptime Kuma to inherit site.domain
- Update docker-compose.yml and Dockerfile to accept dynamic SITE_URL and SITE_DOMAIN build args
- Add .env to .gitignore
2026-09-13 01:10:20 -05:00
venus b13a50ccee feat: set Gateway 3 as root index and move dispatches to /dispatches/
- Update src/index.njk with Gateway 3 homelab services router and cluster telemetry
- Create src/dispatches.njk for the full-width technical dispatches feed and tag filters
- Update src/_data/site.json navigation hierarchy to reflect root Gateway and /dispatches/
- Update breadcrumbs and return links in post.njk and hero-tile.njk
2026-09-13 01:08:42 -05:00
venus d8fb8a3699 feat: implement dynamic Eleventy tiling blog, modular CSS, and agent customizations
- Add project foundation README.md and root AGENTS.md for pair programming
- Configure .agents/ rules and skills for Eleventy tiling and Obsidian publishing
- Decompose monolithic styles into modular tokens, reset, layout, and component CSS
- Implement Nunjucks layout inheritance (base.njk, post.njk, page.njk) and reusable partials
- Configure Eleventy 3 with markdown-it-anchor, syntax highlighting, and dynamic collections
- Migrate sample prototypes into dynamic dispatches lander, gateway, and Markdown posts
- Add multi-stage Dockerfile and clean-URL nginx.conf for production serving
2026-09-13 00:49:27 -05:00
11 changed files with 84 additions and 2491 deletions
+5 -3
View File
@@ -1,5 +1,7 @@
.git .git
.gitignore .gitignore
Dockerfile .agents
docker-compose.yml node_modules
*.md _site
sample-designs
*.log
+5
View File
@@ -0,0 +1,5 @@
# Environment Template
# Copy to .env and adjust variables as needed:
SITE_DOMAIN=riverrooks.dev
SITE_URL=https://riverrooks.dev
PORT=8080
+8
View File
@@ -0,0 +1,8 @@
node_modules/
_site/
.cache/
.DS_Store
*.log
.env
docker-compose.yml
posts/*
-15
View File
@@ -1,15 +0,0 @@
FROM nginx:1.25-alpine
# Remove default Nginx configuration
RUN rm /etc/nginx/conf.d/default.conf
# Copy custom Nginx security & performance config
COPY nginx.conf /etc/nginx/conf.d/default.conf
# Copy static website files into Nginx server root
COPY . /usr/share/nginx/html
# Expose standard HTTP port
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
-17
View File
@@ -1,17 +0,0 @@
version: '3.8'
services:
web:
build:
context: .
dockerfile: Dockerfile
image: river-rooks-blog:latest
container_name: river_rooks_web
restart: unless-stopped
ports:
- "8080:80"
healthcheck:
test: ["CMD", "wget", "-qO-", "http://localhost:80/"]
interval: 30s
timeout: 5s
retries: 3
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env zsh
set -e
echo "pulling latest build image..."
docker compose pull
# Check which service is running
if docker compose ps blog-blue --filter "status=running" -q | grep -q .; then
CURRENT="blog-blue"
TARGET="blog-green"
else
CURRENT="blog-green"
TARGET="blog-blue"
fi
echo "Current: $CURRENT -> deploying Target: $TARGET"
# TODO dont run if 0 instances running
# Scale current deployment to two instances, keeping the old one and adding a running env of the new one
docker compose up -d --no-deps $TARGET
echo "waiting for $TARGET to pass healthcheck..."
# docker ps --filter 'label=com.docker.compose.service=blog' --filter 'health=starting'
until [[ "$(docker inspect --format '{{.State.Health.Status}}' $(docker compose ps -q $TARGET) 2>/dev/null)" == "healthy" ]]; do
sleep 1
done
# Give traefik backend a few seconds to update
sleep 3
echo "Stopping $CURRENT..."
docker compose stop $CURRENT
docker compose rm -f $CURRENT
echo "Zero downtime deployment to $TARGET complete."
# TODO zero downtime deployment
# TODO run on a cron job
File diff suppressed because it is too large Load Diff
-780
View File
@@ -1,780 +0,0 @@
<!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
@@ -1,81 +0,0 @@
# 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
@@ -1,592 +0,0 @@
<!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>
+30
View File
@@ -0,0 +1,30 @@
x-blog-templage: &blog-base
image: git.riverrooks.dev/venus/river-rooks-blog:latest
restart: unless-stopped
volumes:
- "./posts:/app/src/posts"
environment:
- "SITE_DOMAIN=${SITE_DOMAIN:?Error: SITE_DOMAIN is not set. Please define SITE_DOMAIN in .env}"
- "SITE_URL=${SITE_URL:-https://${SITE_DOMAIN}}"
- "PORT=${PORT:-8080}"
- "WATCH=true"
- "API_SECRET=${API_SECRET}"
ports:
- "${HOST_PORT:-${PORT:-8080}}:${PORT:-8080}"
networks:
- backend
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:${PORT:-8080}/healthz > /dev/null || exit 1"]
interval: 5s
timeout: 3s
retries: 3
start_period: 5s
# I Have this deployed with a series of traefik labels but that's not strictly nessescary
services:
blog-blue:
<<: *blog-base
blog-green:
<<: *blog-base
networks:
backend:
external: true