Resume-Website/blog/Homelab/index.html

749 lines
28 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Home Sweet Homelab</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--deep-navy: #0A1128;
--rich-gold: #D4AF37;
--warm-gold: #F4E5C2;
--cream: #FFF8E7;
--charcoal: #2C2C2C;
--silver: #C0C0C0;
--burgundy: #7C2D37;
}
body {
font-family: 'Didot', 'Bodoni MT', 'Playfair Display', Georgia, serif;
line-height: 1.8;
color: var(--charcoal);
background: linear-gradient(180deg, var(--deep-navy) 0%, #1a2847 100%);
position: relative;
overflow-x: hidden;
}
/* Art Deco geometric background pattern */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-image:
repeating-linear-gradient(45deg, transparent, transparent 35px, rgba(212, 175, 55, 0.03) 35px, rgba(212, 175, 55, 0.03) 70px),
repeating-linear-gradient(-45deg, transparent, transparent 35px, rgba(212, 175, 55, 0.03) 35px, rgba(212, 175, 55, 0.03) 70px);
z-index: -1;
}
/* Navigation */
nav {
background: rgba(10, 17, 40, 0.95);
border-bottom: 2px solid var(--rich-gold);
padding: 1.0rem 0;
position: sticky;
top: 0;
z-index: 100;
backdrop-filter: blur(10px);
}
.nav-content {
max-width: 1000px;
margin: 0 auto;
padding: 0 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.nav-logo {
font-size: 1.5rem;
font-weight: 300;
color: var(--warm-gold);
text-decoration: none;
letter-spacing: 0.2em;
text-transform: uppercase;
}
.nav-back {
color: var(--silver);
text-decoration: none;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 0.9rem;
letter-spacing: 0.1em;
text-transform: uppercase;
transition: color 0.3s;
}
.nav-back:hover {
color: var(--rich-gold);
}
.nav-back::before {
content: '←';
margin-right: 0.5rem;
}
/* Article Container */
.article-container {
max-width: 1000px;
margin: 4rem auto;
padding: 0 2rem;
}
/* Article Header */
.article-header {
background: var(--cream);
padding: 4rem 4rem 3rem;
border: 2px solid var(--rich-gold);
position: relative;
margin-bottom: 3rem;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
}
/* Corner decorations */
.article-header::before,
.article-header::after {
content: '';
position: absolute;
width: 60px;
height: 60px;
border: 2px solid var(--rich-gold);
}
.article-header::before {
top: 0;
left: 0;
border-right: none;
border-bottom: none;
}
.article-header::after {
bottom: 0;
right: 0;
border-left: none;
border-top: none;
}
.article-number {
font-size: 4rem;
font-weight: 300;
color: var(--rich-gold);
line-height: 1;
margin-bottom: 1rem;
}
.article-meta {
display: flex;
gap: 2rem;
margin-bottom: 2rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.15em;
}
.article-category {
color: var(--burgundy);
font-weight: 700;
position: relative;
padding-left: 1rem;
}
.article-category::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%) rotate(45deg);
width: 6px;
height: 6px;
background: var(--rich-gold);
}
.article-title {
font-size: 3.5rem;
font-weight: 400;
color: var(--deep-navy);
line-height: 1.2;
letter-spacing: 0.02em;
margin-bottom: 1.5rem;
}
.article-subtitle {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 1.25rem;
line-height: 1.6;
color: var(--charcoal);
font-weight: 400;
}
/* Tech Stack */
.tech-stack {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-top: 2rem;
padding-top: 2rem;
border-top: 2px solid var(--rich-gold);
}
.tech-item {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 0.9rem;
font-weight: 600;
color: var(--deep-navy);
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, rgba(212, 175, 55, 0.15), rgba(212, 175, 55, 0.05));
border-left: 3px solid var(--rich-gold);
letter-spacing: 0.05em;
}
/* Featured Image */
.featured-image {
width: 100%;
height: 500px;
background: linear-gradient(135deg, var(--deep-navy) 0%, var(--burgundy) 100%);
border: 2px solid var(--rich-gold);
margin-bottom: 3rem;
position: relative;
overflow: hidden;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
}
/* Sunburst pattern on image */
.featured-image::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 200%;
height: 200%;
background: repeating-conic-gradient(
from 0deg,
rgba(212, 175, 55, 0.1) 0deg 10deg,
transparent 10deg 20deg
);
transform: translate(-50%, -50%);
}
.image-placeholder {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
color: var(--warm-gold);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 1.2rem;
letter-spacing: 0.1em;
text-transform: uppercase;
}
/* Article Content */
.article-content {
background: var(--cream);
padding: 4rem;
border: 2px solid var(--rich-gold);
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
}
.article-content h2 {
font-size: 2.5rem;
font-weight: 400;
color: var(--deep-navy);
margin: 3rem 0 1.5rem;
letter-spacing: 0.02em;
position: relative;
}
.article-content h2::after {
content: '';
position: absolute;
bottom: -0.5rem;
left: 0;
width: 80px;
height: 3px;
background: linear-gradient(90deg, var(--rich-gold), transparent);
}
.article-content h3 {
font-size: 1.75rem;
font-weight: 400;
color: var(--burgundy);
margin: 2rem 0 1rem;
letter-spacing: 0.02em;
}
.article-content p {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 1.1rem;
line-height: 1.8;
margin-bottom: 1.5rem;
color: var(--charcoal);
}
.article-content ul,
.article-content ol {
margin: 1.5rem 0 1.5rem 2rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 1.1rem;
line-height: 1.8;
}
.article-content li {
margin-bottom: 0.75rem;
padding-left: 0.5rem;
}
.article-content ul li::marker {
color: var(--rich-gold);
}
.article-content ol li::marker {
color: var(--rich-gold);
font-weight: 600;
}
.article-content strong {
color: var(--deep-navy);
font-weight: 600;
}
.article-content blockquote {
border-left: 4px solid var(--rich-gold);
padding: 1.5rem 2rem;
margin: 2rem 0;
background: rgba(212, 175, 55, 0.05);
font-style: italic;
color: var(--burgundy);
}
.article-content code {
background: rgba(10, 17, 40, 0.05);
padding: 0.2rem 0.5rem;
border-radius: 3px;
font-family: 'Monaco', 'Courier New', monospace;
font-size: 0.95rem;
color: var(--burgundy);
}
.article-content pre {
background: var(--deep-navy);
color: var(--warm-gold);
padding: 2rem;
border-radius: 4px;
overflow-x: auto;
margin: 2rem 0;
border: 2px solid var(--rich-gold);
}
.article-content pre code {
background: none;
padding: 0;
color: var(--warm-gold);
font-size: 0.95rem;
line-height: 1.6;
}
/* Decorative Divider */
.deco-divider {
display: flex;
align-items: center;
justify-content: center;
margin: 3rem 0;
position: relative;
}
.deco-divider::before,
.deco-divider::after {
content: '';
flex: 1;
height: 2px;
background: linear-gradient(
to right,
transparent,
var(--rich-gold),
transparent
);
}
.diamond {
width: 12px;
height: 12px;
background: var(--rich-gold);
transform: rotate(45deg);
margin: 0 2rem;
}
/* Footer */
footer {
background: rgba(10, 17, 40, 0.95);
color: var(--silver);
text-align: center;
padding: 3rem 2rem;
margin-top: 4rem;
border-top: 2px solid var(--rich-gold);
position: relative;
}
.footer-ornament {
width: 100px;
height: 3px;
background: linear-gradient(90deg, transparent, var(--rich-gold), transparent);
margin: 0 auto 2rem;
}
footer p {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 0.9rem;
letter-spacing: 0.1em;
text-transform: uppercase;
}
/* Responsive Design */
@media (max-width: 768px) {
.article-header {
padding: 2rem 1.5rem;
}
.article-number {
font-size: 3rem;
}
.article-title {
font-size: 2rem;
}
.article-subtitle {
font-size: 1rem;
}
.article-content {
padding: 2rem 1.5rem;
}
.article-content h2 {
font-size: 1.75rem;
}
.article-content h3 {
font-size: 1.35rem;
}
.article-content p,
.article-content ul,
.article-content ol {
font-size: 1rem;
}
.tech-stack {
gap: 0.5rem;
}
.tech-item {
font-size: 0.8rem;
padding: 0.5rem 1rem;
}
}
</style>
</head>
<body>
<!-- Navigation -->
<nav>
<div class="nav-content">
<a href="#" class="nav-logo">Projects</a>
<a href="https://steen.run/blog/" class="nav-back">Back to Home</a>
</div>
</nav>
<!-- Article Container -->
<div class="article-container">
<!-- Article Header -->
<header class="article-header">
<div class="article-number">01</div>
<div class="article-meta">
<span class="article-category">Infrastructure</span>
<span class="article-date">November 2025</span>
</div>
<h1 class="article-title">Welcome to my homelab</h1>
<p class="article-subtitle">
A tour through my digital home: don't touch anything.
</p>
<div class="tech-stack">
<span class="tech-item">Kubernetes</span>
<span class="tech-item">LGTM Stack</span>
<span class="tech-item">Pi-hole</span>
<span class="tech-item">TrueNAS</span>
<span class="tech-item">Home Assistant</span>
<span class="tech-item">ZFS</span>
</div>
</header>
<!-- Featured Image -->
<div class="featured-image">
<div class="image-placeholder">Homelab Infrastructure</div>
</div>
<!-- Article Content -->
<article class="article-content">
<p>
You know how when someone's coming over, you do that frantic cleaning spree where you start
shoving everything into closets and you try and convince everyone that you're always this neat? This article is like
that, but for my homelab. The actual setup is a stratographic column of different ideas and experiments from different time periods that are forced to work together to form a cohesive unit.
What I'm about to talk about are the Wins that have solved problems for me and made my life easier.
</p>
<p>
Let me give you the tour.
</p>
<div class="deco-divider">
<div class="diamond"></div>
</div>
<h2>The Kubernetes Cluster</h2>
<p>
I got into Kubernetes the hard way. I had a handful of services running on various VMs, each with their
own quirks and manual deployment steps. Updating anything meant SSH-ing into the right machine, remembering
which directory the config lived in, and hoping I didn't fat-finger something in production. After the third
time I had to delete a VM because I couldn't remember a password, I decided there had to be a better way.
</p>
<p>
Right now, I'm running a single-node cluster. Both the control plane and worker live on the same machine,
which is like saying you ride a motorcycle, and it's actually a moped. The plan is to expand this into a proper multi-node setup
eventually. For now, it's handling everything I need: automated deployments, rolling updates, and the kind
of infrastructure-as-code setup that lets me sleep at night knowing I can rebuild everything from YAML if I
need to.
</p>
<p>
The nice thing about Kubernetes is that once you get past the initial learning cliff,
deploying new services becomes almost trivial. Write a manifest, apply it, and
watch it spin up. No more manual configuration files scattered across different machines.
Services get their own networks. Routing is included. You would need a vastly more complex
solution to do what Kubernetes does even at a basic level.
</p>
<div class="deco-divider">
<div class="diamond"></div>
</div>
<h2>Monitoring with LGTM</h2>
<p>
If Kubernetes is the engine, then monitoring is the dashboard. I'm running the full LGTM stack: Loki for
logs, Grafana for visualization, Tempo for traces, and Mimir for metrics. It sounds like overkill for a
homelab, and maybe it is, but I got tired of playing detective every time something went sideways.
</p>
<p>
Every time a container would fail, it would restart, taking its logs to the grave with it. I would be
none-the-wiser about what actually happened. Kubernetes is excellent for keeping uptime- well, up. But
it masks the problem. This would be a reason why some tech-centric people prefer hard failures over soft
failures. Hard failures can address the problem. Soft failures let the problems pile up under the rug.
</p>
<p>
Now I can pull up Grafana, see exactly when CPU spiked, cross-reference it with logs in Loki, and actually
understand what my problem is. Also, there's something satisfying about having charts and graphs that
show your systems are healthy. Or unhealthy. At least you know. Imagine if your own body had such charts.
People would be checking them every day.
</p>
<div class="deco-divider">
<div class="diamond"></div>
</div>
<h2>DNS Redundancy with Pi-hole</h2>
<p>
Here's a fun scenario: you're rebooting your homelab for updates or it shuts down after a power outage.
You wake up and your phone doesn't work. You try to connect to your Pi-hole instance to see the problem,
but your computer doesn't have an IP. So now you can't change your DNS/DHCP configuration, because your
computer can't talk to the device that should be managing that. To solve the problem you would have had
to have solved the problem already. A Catch-22.
</p>
<p>
This happened to me several times before I set up a Raspberry Pi running Pi-hole as a fallback. It sits
outside the main homelab infrastructure, always on, always ready. When the primary DNS is up, it's just a
redundant backup. When I need to restart things, it seamlessly takes over. I rsync the config to the backup
Pi once a day to keep them consistent, or provide a good backup should I really screw something up.
</p>
<p>
As a bonus, Pi-hole blocks ads at the network level, which is it's main benefit, but I almost forget about
it until I get off my network. It's such a robust product that I only remember it when I want to mess with
DNS/DHCP (break things).
</p>
<div class="deco-divider">
<div class="diamond"></div>
</div>
<h2>TrueNAS for Storage</h2>
<p>
Storage is one of those problems that sneaks up on you. First it's just a few documents. Then some photos.
Then you're ripping your DVD collection and suddenly you need 10TB and redundancy because losing years of
data to a single drive failure sounds like a nightmare.
</p>
<p>
TrueNAS handles all of this with ZFS under the hood. I've got it set up with mirrored drives, which means
I can lose a disk without losing data. It serves media to devices around the house, backs up important files,
and generally acts as the single source of truth for anything I don't want to lose.
</p>
<p>
The web interface makes it approachable too. I'm not particularly interested in becoming a storage expert,
but TrueNAS lets me configure RAID levels, set up snapshots, and monitor drive health without needing a PhD
in filesystems. It just works, which is exactly what storage should do.
</p>
<div class="deco-divider">
<div class="diamond"></div>
</div>
<h2>Network Gear and Routing</h2>
<p>
A good homelab needs a solid network foundation. I'm running a managed switch that handles VLANs, which lets
me segment traffic appropriately. IoT devices get their own VLAN, which means even if some random smart bulb
gets compromised, it can't reach the rest of the network. It also brings me some sense of organization. My
Rasperry Pi cluster can stay organized on one VLAN without mucking up my devices list.
</p>
<p>
The router is running custom firmware that gives me way more control than any consumer router ever would. I
can shape traffic, set up VPNs, and actually see what's happening on my network. It's the kind of thing where
once you start looking at the data, you realize how much garbage is constantly flying around your home network.
Smart TVs phoning home. IoT devices pinging random servers. It's enlightening in a slightly creepy way.
</p>
<div class="deco-divider">
<div class="diamond"></div>
</div>
<h2>Backup Strategy</h2>
<p>
Here's something nobody thinks about until it's too late: backups. The homelab itself is somewhat ephemeral.
I can rebuild it from configs. But the data? That needs to be protected.
</p>
<p>
I'm following the 3-2-1 rule: three copies of data, on two different types of media, with one copy offsite (well sorta).
TrueNAS handles the local copies with snapshots and redundancy. It's in a ZRAID configuration for drive parity. I've
got an external drive I have plugged into the homelab using a docking station that I will periodically turn on. I have
most of my configs backed up to my desktop or in the cloud, so in the event of a failure, I can get back online quick.
</p>
<p>
It's not exciting. It's not fun to set up. I've actually found I've misconfigured it several times and
through sheer luck that hasn't been a problem yet. But it's the difference between a minor inconvenience and a
catastrophic loss. I learned this lesson on my desktop when I was dual booting Windows and Linux. Windows
introduced a bug that deleted non-Windows boot drives. Now everything is backed up, automated, and I can actually sleep at night.
</p>
<div class="deco-divider">
<div class="diamond"></div>
</div>
<h2>Automation and Services</h2>
<p>
The real power of a homelab comes from what you do with it. I've got Home Assistant running for home automation,
which ties together all the random smart devices I've accumulated over the years. Lights, sensors, you name it, all
talking to each other in ways they were never designed to. At some point in the future, I'd like to replace my
light switches with dimmers that can be run by Home Assistant, so they will dim or brighten depending on the
time of day.
</p>
<p>
Kubernetes ingress reverse proxying handles all the web service routing, complete with automatic SSL certificates. I don't have to
think about certificate renewal or manually configuring HTTPS anymore. Cert-manager does that all for me.
</p>
<p>
I've got a Gitea server for personal projects, a password manager to sync across devices, an Obsidian-livesync instance for my note taking,
and a handful of other services that make daily life a little easier. Each one solves a specific problem, and
together they form an ecosystem that's genuinely useful.
</p>
<p>
My last ditch effort for automation is n8n. It is what I would call "code glue." Any services that need to talk to each other
in some way, but don't have preconfigured methods to do so, I can stitch together with n8n. It allows me to create workflows,
use logic, create webhooks, and modify data before sending it somewhere else. If I need something done and there's no right way
to do it, you bet I'm gonna make an n8n workflow.
</p>
<div class="deco-divider">
<div class="diamond"></div>
</div>
<h2>Power Management</h2>
<p>
Something people don't talk about enough: power. Not just whether things are plugged in, but power management
during outages. I've got a UPS (Unattended Power Supply) handling the critical infrastructure. It's not designed
to keep things running for hours, but it gives me enough time to gracefully shut everything down if the power
goes out.
</p>
<p>
More importantly, it protects against surges and brown-outs. Electronics are expensive, and replacing a failed
server because of dirty power is both costly and annoying. The UPS communicates with the servers too, so if I'm
not home and the power goes out, everything shuts down safely on its own.
</p>
<div class="deco-divider">
<div class="diamond"></div>
</div>
<h2>Constant Evolution</h2>
<p>
One morning I woke up to find my phone disconnected from the network. To my surprise, it wasn't Pi-holes fault,
it was a drive failure. Not a ZRAIDed TrueNAS drive, but the main one. I had misconfigured backups at this point.
Upon restart, my homelab wouldn't boot. I was in a pickle, but I didn't dispair. I thought it would be a chance to
start anew. I thought about many of the changes I would make.
</p>
<p>
First of all, I would get rid of Proxmox. I had it installed as a Type II hypervisor on the OS level. I was <del>conned</del>
meme-d into installing it, only to find it overly complex to do simple things. Worst of all, I had absolutely no use for a VM.
I was already comfortable with containers. If I needed a GPU I could theoretically do a passthrough to a VM, but the convoluted
abstractions always made it impossible to actually configure applications with it. I would rather run an OS and run containers/VMs
as need be than just abstract the functionality away.
</p>
<p>
Argo CD. A deified tool among some. A waste of resources to me. For example: any Argo CD instance needs another host of Argo CD service
instances that can be quite beefy on any system. And for what? To do what Kubernetes does automatically? You would be better off
creating a pipeline orchestrator from scratch using cron, webhook scripts, and Terraform.
</p>
<p>
There's a happy ending to the story. Eventually, after rebooting the homelab a couple dozen times trying and failing to hit the boot
menu key fast enough, I discovered that my drive hadn't failed at all. It works even now, and for no reason at all. Amazing.
</p>
<p>
I still have future plans for my homelab. If I ever come into some more hardware, I could finally set up that second node for
my Kubernetes cluster. I might even leave it bare-metal so I can keep the GPU available for any future projects where I might want
some ubiquitous computing power anywhere in the world. It would also be nice to move TrueNAS to it's own dedicated hardware.
There's always more to learn and experiment with. I wonder what sort of homelab I could have a year from now.
</p>
</article>
</div>
<!-- Footer -->
<footer>
<div class="footer-ornament"></div>
<p>Tech Chronicals 2025</p>
</footer>
</body>
</html>