Compare commits
23
Commits
c0f67e75a3
...
server
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bf5e83964 | ||
|
|
d6c5ed1d3b | ||
|
|
528c302c74 | ||
|
|
66c4e89732 | ||
|
|
60f77838a0 | ||
|
|
04a39ca852 | ||
|
|
eddbf5470e | ||
|
|
acd47b80aa | ||
|
|
900014f506 | ||
|
|
737a6e3e51 | ||
|
|
2423546ee6 | ||
|
|
d55297f1dd | ||
|
|
d3aefa343e | ||
|
|
78b1e0b485 | ||
|
|
e733f479dd | ||
|
|
1079b956d3 | ||
|
|
125b3e8839 | ||
|
|
cedd8194f6 | ||
|
|
e4601dbbcb | ||
|
|
fc6c17b541 | ||
|
|
1491d3fa5c | ||
|
|
b13a50ccee | ||
|
|
d8fb8a3699 |
+5
-3
@@ -1,5 +1,7 @@
|
|||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
Dockerfile
|
.agents
|
||||||
docker-compose.yml
|
node_modules
|
||||||
*.md
|
_site
|
||||||
|
sample-designs
|
||||||
|
*.log
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
node_modules/
|
||||||
|
_site/
|
||||||
|
.cache/
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
docker-compose.yml
|
||||||
|
posts/*
|
||||||
-15
@@ -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;"]
|
|
||||||
@@ -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
|
|
||||||
Executable
+36
@@ -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
@@ -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 & 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 & 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">> 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 • ~/NOTES/INDEX.MD</div>
|
|
||||||
<h1 class="hero-main-title">Engineering Notes, CTF Write-Ups & 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 • 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 • ~/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 →</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">> Embedded & 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">> Network Routing & 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">> 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 • 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">> 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 →</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 • 6 min read</time>
|
|
||||||
<span>~/notes/posix-sh-vs-bash.md</span>
|
|
||||||
</div>
|
|
||||||
<h3 style="margin: 0;">
|
|
||||||
<a href="#" class="feed-heading">> 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 →</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 • 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">> 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 →</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 • 10 min read</time>
|
|
||||||
<span>~/notes/zero-trust-wireguard-opnsense.md</span>
|
|
||||||
</div>
|
|
||||||
<h3 style="margin: 0;">
|
|
||||||
<a href="#" class="feed-heading">> Zero-Trust Network Isolation with WireGuard & 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 →</a>
|
|
||||||
</div>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- Global Footer Tile (Matching Gateway 3 Exactly) -->
|
|
||||||
<footer class="footer-tile">
|
|
||||||
<div>River Rooks • 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>
|
|
||||||
@@ -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.
|
|
||||||
@@ -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">> Blog & 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 • RACK-01 NODE GATEWAY</div>
|
|
||||||
<h1 class="hero-main-title">Engineering Sovereign Infrastructure & 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> •
|
|
||||||
<span>[2] Forgejo Git Repos</span> •
|
|
||||||
<span>[3] Public Cryptographic & PDF Tools</span> •
|
|
||||||
<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/ • 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">> River Rooks Technical Notes & 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] →</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] →</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] →</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] →</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 & 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 & 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 & 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 • 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>
|
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user