laforceit-blog/backup-20250422/index.astro

567 lines
17 KiB
Plaintext

---
import { getCollection } from 'astro:content';
import type { CollectionEntry } from 'astro:content';
import BaseLayout from '../layouts/BaseLayout.astro';
import DigitalGardenGraph from '../components/DigitalGardenGraph.astro';
type Post = CollectionEntry<'posts'>;
type Config = CollectionEntry<'configurations'>;
type Project = CollectionEntry<'projects'>;
// Get all blog posts (excluding configurations and specific guides)
const posts = (await getCollection('blog'))
.filter(item =>
!item.slug.startsWith('configurations/') &&
!item.slug.startsWith('projects/') &&
!item.data.category?.toLowerCase().includes('configuration') &&
!item.slug.includes('setup-guide') &&
!item.slug.includes('config')
)
.sort((a, b) => new Date(b.data.pubDate || 0).valueOf() - new Date(a.data.pubDate || 0).valueOf());
// Get configuration posts
const configurations = (await getCollection('blog'))
.filter(item =>
item.slug.startsWith('configurations/') ||
item.data.category?.toLowerCase().includes('configuration') ||
item.slug.includes('setup-guide') ||
item.slug.includes('config') ||
item.slug.includes('monitoring') ||
item.slug.includes('server') ||
item.slug.includes('tunnel')
)
.sort((a, b) => new Date(b.data.pubDate || 0).valueOf() - new Date(a.data.pubDate || 0).valueOf());
// Get project posts
const projects = (await getCollection('blog'))
.filter(item =>
item.slug.startsWith('projects/') ||
item.data.category?.toLowerCase().includes('project')
)
.sort((a, b) => new Date(b.data.pubDate || 0).valueOf() - new Date(a.data.pubDate || 0).valueOf());
---
<BaseLayout title="LaForce IT - Home Lab & DevOps Insights">
<!-- Hero section -->
<section class="hero">
<div class="hero-content">
<div class="hero-subtitle">Home Lab & DevOps</div>
<h1 class="hero-title">Exploring <span>advanced infrastructure</span> and automation</h1>
<p class="hero-description">
Join me on a journey through enterprise-grade home lab setups, Kubernetes deployments, and DevOps best practices for the modern tech enthusiast.
</p>
<div class="social-links-hero">
<a href="https://github.com/keyargo" target="_blank" rel="noopener noreferrer" class="social-link-hero github">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
</a>
<a href="https://linkedin.com/in/danlaforce" target="_blank" rel="noopener noreferrer" class="social-link-hero linkedin">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
</a>
</div>
<a href="#posts" class="cta-button">
Explore Latest Posts
</a>
</div>
<div class="terminal-box">
<div class="terminal-header">
<div class="terminal-dots">
<div class="terminal-dot terminal-dot-red"></div>
<div class="terminal-dot terminal-dot-yellow"></div>
<div class="terminal-dot terminal-dot-green"></div>
</div>
<div class="terminal-title">argobox:~/homelab</div>
</div>
<div class="terminal-content">
<div class="terminal-line">
<span class="terminal-prompt">$</span>
<span class="terminal-command">kubectl get nodes</span>
</div>
<div class="terminal-output">
NAME STATUS ROLES AGE VERSION<br>
argobox Ready &lt;none&gt; 47d v1.28.3+k3s1<br>
argobox-lite Ready control-plane,master 47d v1.28.3+k3s1
</div>
<div class="terminal-line">
<span class="terminal-prompt">$</span>
<span class="terminal-command">helm list -A</span>
</div>
<div class="terminal-output">
NAME NAMESPACE REVISION STATUS CHART<br>
cloudnative-pg postgres 1 deployed cloudnative-pg-0.18.0<br>
prometheus monitoring 2 deployed kube-prometheus-stack-51.2.0
</div>
<div class="terminal-line">
<span class="terminal-prompt">$</span>
<span class="terminal-command terminal-typing">cloudflared tunnel status</span>
</div>
</div>
</div>
</section>
<!-- Digital Garden Visualization -->
<section class="container">
<h2 class="section-title">My Digital Garden</h2>
<p class="digital-garden-intro">
This blog functions as my personal digital garden - a collection of interconnected ideas, guides, and projects.
Browse through the visualization below to see how different concepts relate to each other.
</p>
<DigitalGardenGraph />
</section>
<!-- Main content sections -->
<main class="container">
<section id="posts" class="mb-16">
<h2 class="section-title">Latest Posts</h2>
<div class="blog-grid">
{posts.map((post) => (
<article class="post-card">
{post.data.heroImage ? (
<img
width={720}
height={360}
src={post.data.heroImage}
alt=""
class="post-image"
/>
) : (
<img
width={720}
height={360}
src="/blog/images/placeholders/default.jpg"
alt=""
class="post-image"
/>
)}
<div class="post-content">
<div class="post-meta">
<time datetime={post.data.pubDate ? new Date(post.data.pubDate).toISOString() : ''}>
{post.data.pubDate ? new Date(post.data.pubDate).toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
}) : 'No date'}
</time>
{post.data.category && (
<span class="post-category">
{post.data.category}
</span>
)}
</div>
<h3 class="post-title">
<a href={`/blog/${post.slug}/`}>{post.data.title}</a>
{post.data.draft && <span class="ml-2 px-2 py-1 bg-gray-200 text-gray-700 text-xs rounded">Draft</span>}
</h3>
<p class="post-excerpt">{post.data.description}</p>
<div class="post-footer">
<span class="post-read-time">{post.data.readTime || '5 min read'}</span>
<a href={`/blog/${post.slug}/`} class="read-more">Read More</a>
</div>
</div>
</article>
))}
</div>
</section>
<section id="configurations" class="mb-16">
<h2 class="section-title">Configurations</h2>
<div class="blog-grid">
{configurations.map((config) => (
<article class="post-card">
{config.data.heroImage ? (
<img
width={720}
height={360}
src={config.data.heroImage}
alt=""
class="post-image"
/>
) : (
<img
width={720}
height={360}
src="/blog/images/placeholders/default.jpg"
alt=""
class="post-image"
/>
)}
<div class="post-content">
<div class="post-meta">
<time datetime={config.data.pubDate ? new Date(config.data.pubDate).toISOString() : ''}>
{config.data.pubDate ? new Date(config.data.pubDate).toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
}) : 'No date'}
</time>
{config.data.category && (
<span class="post-category">
{config.data.category}
</span>
)}
</div>
<h3 class="post-title">
<a href={`/blog/${config.slug}/`}>{config.data.title}</a>
{config.data.draft && <span class="ml-2 px-2 py-1 bg-gray-200 text-gray-700 text-xs rounded">Draft</span>}
</h3>
<p class="post-excerpt">{config.data.description}</p>
<div class="post-footer">
<span class="post-read-time">{config.data.readTime || '5 min read'}</span>
<a href={`/blog/${config.slug}/`} class="read-more">Read More</a>
</div>
</div>
</article>
))}
</div>
</section>
<section id="projects" class="mb-16">
<h2 class="section-title">Projects</h2>
<div class="blog-grid">
{projects.map((project) => (
<article class="post-card">
{project.data.heroImage ? (
<img
width={720}
height={360}
src={project.data.heroImage}
alt=""
class="post-image"
/>
) : (
<img
width={720}
height={360}
src="/blog/images/placeholders/default.jpg"
alt=""
class="post-image"
/>
)}
<div class="post-content">
<div class="post-meta">
<time datetime={project.data.pubDate ? new Date(project.data.pubDate).toISOString() : ''}>
{project.data.pubDate ? new Date(project.data.pubDate).toLocaleDateString('en-us', {
year: 'numeric',
month: 'short',
day: 'numeric',
}) : 'No date'}
</time>
{project.data.category && (
<span class="post-category">
{project.data.category}
</span>
)}
</div>
<h3 class="post-title">
<a href={`/blog/${project.slug}/`}>{project.data.title}</a>
{project.data.draft && <span class="ml-2 px-2 py-1 bg-gray-200 text-gray-700 text-xs rounded">Draft</span>}
</h3>
{project.data.technologies && (
<div class="mb-2 flex flex-wrap gap-2">
{project.data.technologies.map((tech) => (
<span class="post-category">
{tech}
</span>
))}
</div>
)}
<p class="post-excerpt">{project.data.description}</p>
<div class="post-footer">
<div class="flex gap-4">
{project.data.github && (
<a href={project.data.github} target="_blank" rel="noopener noreferrer" class="read-more">
GitHub
</a>
)}
{project.data.live && (
<a href={project.data.live} target="_blank" rel="noopener noreferrer" class="read-more">
Live Demo
</a>
)}
</div>
<a href={`/blog/${project.slug}/`} class="read-more">View Project</a>
</div>
</div>
</article>
))}
</div>
</section>
<!-- Featured section -->
<section class="featured-section">
<div class="featured-grid">
<div class="featured-content">
<div class="featured-subtitle">Featured Project</div>
<h2 class="featured-title">ArgoBox <span>Home Lab Architecture</span></h2>
<p class="featured-description">
A complete enterprise-grade home infrastructure built on Kubernetes, featuring high availability, zero-trust networking, and fully automated deployments.
</p>
<ul class="featured-list">
<li class="featured-list-item">
<div class="featured-list-icon">✓</div>
<div>Multi-node K3s cluster with automatic failover</div>
</li>
<li class="featured-list-item">
<div class="featured-list-icon">✓</div>
<div>Gitea + Flux CD for GitOps-based continuous deployment</div>
</li>
<li class="featured-list-item">
<div class="featured-list-icon">✓</div>
<div>Cloudflare Tunnels for secure, zero-trust remote access</div>
</li>
<li class="featured-list-item">
<div class="featured-list-icon">✓</div>
<div>Synology NAS integration with Kubernetes volumes</div>
</li>
</ul>
<a href="#" class="cta-button">
View Project Details
</a>
</div>
</div>
</section>
<!-- About Me Section -->
<section class="about-section mb-16">
<h2 class="section-title">About Me</h2>
<div class="about-content">
<div class="about-text">
<p>
Hi, I'm Daniel LaForce, a passionate DevOps and infrastructure engineer with a focus on Kubernetes,
automation, and cloud technologies. When I'm not working on enterprise systems, I'm building and
refining my home lab environment to test and learn new technologies.
</p>
<p>
This site serves as both my technical blog and digital garden - a place to share what I've learned
and document my ongoing projects. Feel free to connect with me on GitHub or LinkedIn!
</p>
<div class="social-links">
<a href="https://github.com/keyargo" target="_blank" rel="noopener noreferrer" class="social-link">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22"></path></svg>
<span>GitHub</span>
</a>
<a href="https://linkedin.com/in/danlaforce" target="_blank" rel="noopener noreferrer" class="social-link">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"></path><rect x="2" y="9" width="4" height="12"></rect><circle cx="4" cy="4" r="2"></circle></svg>
<span>LinkedIn</span>
</a>
</div>
</div>
</div>
</section>
</main>
</BaseLayout>
<style>
.featured-section {
margin-top: 4rem;
background: var(--card-bg);
border-radius: 1rem;
border: 1px solid var(--card-border);
padding: 2rem;
position: relative;
overflow: hidden;
}
.featured-grid {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
}
.featured-subtitle {
font-family: 'JetBrains Mono', monospace;
color: var(--accent-primary);
font-size: 0.9rem;
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 1rem;
}
.featured-title {
font-size: clamp(1.8rem, 4vw, 2.5rem);
line-height: 1.2;
margin-bottom: 1.5rem;
}
.featured-title span {
background: linear-gradient(90deg, var(--accent-primary), var(--accent-tertiary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.featured-description {
color: var(--text-secondary);
font-size: 1.1rem;
margin-bottom: 1.5rem;
max-width: 600px;
}
.featured-list {
list-style: none;
margin-bottom: 2rem;
}
.featured-list-item {
display: flex;
margin-bottom: 0.75rem;
align-items: flex-start;
}
.featured-list-icon {
color: var(--accent-primary);
margin-right: 1rem;
font-weight: bold;
}
.mb-16 {
margin-bottom: 4rem;
}
.flex {
display: flex;
}
.flex-wrap {
flex-wrap: wrap;
}
.gap-2 {
gap: 0.5rem;
}
.gap-4 {
gap: 1rem;
}
.ml-2 {
margin-left: 0.5rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.py-1 {
padding-top: 0.25rem;
padding-bottom: 0.25rem;
}
.bg-gray-200 {
background-color: rgba(226, 232, 240, 0.2);
}
.text-gray-700 {
color: #94a3b8;
}
.text-xs {
font-size: 0.75rem;
}
.rounded {
border-radius: 0.25rem;
}
.social-links-hero {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.social-link-hero {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: var(--card-bg);
color: var(--text-primary);
transition: all 0.3s ease;
border: 1px solid var(--card-border);
}
.social-link-hero:hover {
transform: translateY(-3px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
}
.social-link-hero.github:hover {
background-color: #24292e;
border-color: #24292e;
}
.social-link-hero.linkedin:hover {
background-color: #0077b5;
border-color: #0077b5;
}
.about-section {
background: var(--card-bg);
border-radius: 1rem;
border: 1px solid var(--card-border);
padding: 2rem;
position: relative;
overflow: hidden;
}
.about-content {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
}
.about-text {
color: var(--text-secondary);
font-size: 1.1rem;
line-height: 1.6;
}
.about-text p {
margin-bottom: 1.5rem;
}
.social-links {
display: flex;
gap: 1.5rem;
margin-top: 2rem;
}
.social-link {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--text-primary);
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
background-color: rgba(226, 232, 240, 0.05);
transition: all 0.3s ease;
}
.social-link:hover {
background-color: rgba(226, 232, 240, 0.1);
transform: translateY(-2px);
}
@media (min-width: 768px) {
.featured-grid {
grid-template-columns: 1fr;
}
.about-content {
grid-template-columns: 1fr;
}
}
@media (min-width: 1024px) {
.about-content {
grid-template-columns: 1fr;
}
}
</style>