567 lines
17 KiB
Plaintext
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 <none> 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>
|