feat: consolidate homelab projects, add skills dropdown, update timeline animations
Build & Push Portfolio Image / build-push (push) Successful in 2m33s
Build & Push Portfolio Image / build-push (push) Successful in 2m33s
- Consolidate 3 homelab projects into single 'Self-Hosted Cloud' card - Add /homelab deep dive page with full tech stack - Update to 4 nodes (3 control plane + 1 worker) - Add organized skills dropdown (6 categories) - Update timeline skills for riotpiao.com experience - Add 'Learn more' links for all major experiences - Change timeline animation to scroll-reveal (y-axis)
This commit is contained in:
@@ -0,0 +1,244 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import Link from 'next/link'
|
||||||
|
import { ArrowLeft } from 'lucide-react'
|
||||||
|
|
||||||
|
const skillCategories = [
|
||||||
|
{
|
||||||
|
title: 'Infrastructure & Orchestration',
|
||||||
|
skills: ['Kubernetes (Talos Linux)', 'ArgoCD (GitOps)', 'Terraform (IaC)', 'Kustomize'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Networking & Ingress',
|
||||||
|
skills: ['nginx Ingress Controller', 'Cloudflare Tunnel', 'CoreDNS', 'NetworkPolicy'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Storage',
|
||||||
|
skills: ['Longhorn (distributed block)', 'MinIO (S3-compatible)'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Databases',
|
||||||
|
skills: ['CloudNativePG (PostgreSQL)', 'pgvector (AI embeddings)'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Identity & Security',
|
||||||
|
skills: ['Authentik (OIDC SSO)', 'SOPS (encrypted secrets)', 'cert-manager (TLS)'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Observability',
|
||||||
|
skills: ['Prometheus', 'Grafana', 'Tempo (tracing)', 'OpenTelemetry', 'Loki (logs)'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'CI/CD',
|
||||||
|
skills: ['Forgejo (git + Actions)', 'Container Registry', 'DinD Runners'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'AI/ML Platform',
|
||||||
|
skills: ['vLLM (Qwen3-32B)', 'Ollama', 'TEI (embeddings)', 'KServe'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Workflow & Messaging',
|
||||||
|
skills: ['Temporal (durable workflows)', 'Kafka/Redpanda (streaming)'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Languages & Frameworks',
|
||||||
|
skills: ['Go (API gateway)', 'Python (ML)', 'Next.js (frontend)'],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const coreCompetencies = [
|
||||||
|
'Multi-tenant GPU scheduling (sm70/Volta constraints)',
|
||||||
|
'Zero-downtime GitOps deployments',
|
||||||
|
'Service mesh patterns without Istio overhead',
|
||||||
|
'Hybrid cloud networking (Cloudflare + bare-metal)',
|
||||||
|
'Declarative IAM with OIDC claim mapping',
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function HomelabPage() {
|
||||||
|
return (
|
||||||
|
<main className="min-h-screen bg-white dark:bg-gray-950">
|
||||||
|
<div className="max-w-4xl mx-auto px-6 py-20">
|
||||||
|
<Link
|
||||||
|
href="/#project-homelab"
|
||||||
|
className="inline-flex items-center gap-2 text-blue-600 dark:text-blue-400 hover:underline mb-8"
|
||||||
|
>
|
||||||
|
<ArrowLeft size={16} />
|
||||||
|
Back to Projects
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<h1 className="text-4xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Homelab: Self-Hosted Cloud Platform
|
||||||
|
</h1>
|
||||||
|
<p className="text-xl text-gray-600 dark:text-gray-400 mb-12">
|
||||||
|
AWS rebuilt from scratch at home—full stack from compute to observability.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{/* Stats */}
|
||||||
|
<section className="bg-gradient-to-r from-blue-50 to-purple-50 dark:from-gray-800 dark:to-gray-800 rounded-lg p-6 mb-12">
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-center">
|
||||||
|
<div>
|
||||||
|
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">4</div>
|
||||||
|
<div className="text-sm text-gray-600 dark:text-gray-400">Nodes</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">20+</div>
|
||||||
|
<div className="text-sm text-gray-600 dark:text-gray-400">Services</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">99.2%</div>
|
||||||
|
<div className="text-sm text-gray-600 dark:text-gray-400">Uptime</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">60%</div>
|
||||||
|
<div className="text-sm text-gray-600 dark:text-gray-400">LLM Latency Cut</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Skills Grid */}
|
||||||
|
<section className="mb-12">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">
|
||||||
|
Technologies & Skills
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{skillCategories.map((category) => (
|
||||||
|
<div
|
||||||
|
key={category.title}
|
||||||
|
className="bg-gray-50 dark:bg-gray-900 rounded-lg p-4 border border-gray-200 dark:border-gray-800"
|
||||||
|
>
|
||||||
|
<h3 className="font-semibold text-gray-900 dark:text-white mb-2">
|
||||||
|
{category.title}
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{category.skills.map((skill) => (
|
||||||
|
<span
|
||||||
|
key={skill}
|
||||||
|
className="text-xs px-2 py-1 rounded bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200"
|
||||||
|
>
|
||||||
|
{skill}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Core Competencies */}
|
||||||
|
<section className="mb-12">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Core Competencies Demonstrated
|
||||||
|
</h2>
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{coreCompetencies.map((item) => (
|
||||||
|
<li key={item} className="flex items-start gap-2 text-gray-700 dark:text-gray-300">
|
||||||
|
<span className="text-blue-500 mt-1">•</span>
|
||||||
|
{item}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Architecture Sections */}
|
||||||
|
<section className="mb-12">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-6">
|
||||||
|
Architecture Deep Dive
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
{/* Infrastructure */}
|
||||||
|
<div className="mb-6 bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
|
||||||
|
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
|
||||||
|
Infrastructure Layer
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-700 dark:text-gray-300 mb-3">
|
||||||
|
4-node bare-metal cluster (3 control plane + 1 worker) running Talos Linux—immutable, API-driven OS designed for Kubernetes.
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<li>• <strong>Compute:</strong> Talos Linux nodes, machine config via Terraform</li>
|
||||||
|
<li>• <strong>Networking:</strong> Cilium CNI, nginx ingress, Cloudflare Tunnel for zero-trust external access</li>
|
||||||
|
<li>• <strong>Storage:</strong> Longhorn for distributed block storage with disk tagging, MinIO for S3</li>
|
||||||
|
<li>• <strong>GitOps:</strong> ArgoCD with multi-source Applications and sync waves</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Data Platform */}
|
||||||
|
<div className="mb-6 bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
|
||||||
|
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
|
||||||
|
Data Platform
|
||||||
|
</h3>
|
||||||
|
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<li>• <strong>PostgreSQL:</strong> CloudNativePG operator with HA, automated failover</li>
|
||||||
|
<li>• <strong>Vector DB:</strong> pgvector extension for AI embeddings</li>
|
||||||
|
<li>• <strong>Streaming:</strong> Kafka/Redpanda for event-driven architecture</li>
|
||||||
|
<li>• <strong>Workflows:</strong> Temporal for durable, long-running processes</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* AI/ML */}
|
||||||
|
<div className="mb-6 bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
|
||||||
|
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
|
||||||
|
AI/ML Platform
|
||||||
|
</h3>
|
||||||
|
<p className="text-gray-700 dark:text-gray-300 mb-3">
|
||||||
|
Self-hosted LLM inference with multi-tenant GPU scheduling and 60% latency reduction.
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<li>• <strong>Inference:</strong> vLLM serving Qwen3-32B with INT4 quantization</li>
|
||||||
|
<li>• <strong>Multi-model:</strong> Ollama for smaller models, hot-swapping</li>
|
||||||
|
<li>• <strong>Embeddings:</strong> TEI for text embeddings and reranking</li>
|
||||||
|
<li>• <strong>Orchestration:</strong> KServe + custom Go API gateway</li>
|
||||||
|
<li>• <strong>Scheduling:</strong> GPU node affinity with sm70/Volta constraints</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Security */}
|
||||||
|
<div className="mb-6 bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
|
||||||
|
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
|
||||||
|
Identity & Security
|
||||||
|
</h3>
|
||||||
|
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<li>• <strong>SSO:</strong> Authentik OIDC provider with custom claims and group mapping</li>
|
||||||
|
<li>• <strong>RBAC:</strong> Kubernetes RBAC synced with Authentik groups</li>
|
||||||
|
<li>• <strong>Secrets:</strong> SOPS-encrypted secrets in git, decrypted at deploy time</li>
|
||||||
|
<li>• <strong>TLS:</strong> cert-manager with DNS-01 ACME challenges via Cloudflare</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Observability */}
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
|
||||||
|
<h3 className="font-semibold text-gray-900 dark:text-white mb-3">
|
||||||
|
Observability Stack
|
||||||
|
</h3>
|
||||||
|
<ul className="space-y-1 text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
<li>• <strong>Metrics:</strong> Prometheus with custom recording rules, Grafana dashboards</li>
|
||||||
|
<li>• <strong>Logs:</strong> Loki for aggregation, structured logging from all services</li>
|
||||||
|
<li>• <strong>Traces:</strong> Tempo + OpenTelemetry Collector for distributed tracing</li>
|
||||||
|
<li>• <strong>Alerts:</strong> AlertManager → Slack integration for incident response</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* Applications */}
|
||||||
|
<section className="mb-12">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4">
|
||||||
|
Self-Hosted Applications
|
||||||
|
</h2>
|
||||||
|
<div className="bg-gray-50 dark:bg-gray-900 rounded-lg p-6 border border-gray-200 dark:border-gray-800">
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{['Paperless-ngx', 'Immich', 'Homarr', 'Portainer', 'Forgejo', 'Authentik'].map((app) => (
|
||||||
|
<span
|
||||||
|
key={app}
|
||||||
|
className="text-sm px-3 py-1 rounded-full bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200"
|
||||||
|
>
|
||||||
|
{app}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
+10
-8
@@ -15,23 +15,25 @@ export default function Home() {
|
|||||||
|
|
||||||
const projects = t.projects.items.map((item, index) => ({
|
const projects = t.projects.items.map((item, index) => ({
|
||||||
...item,
|
...item,
|
||||||
id: index === 3
|
id: index === 0
|
||||||
|
? 'project-homelab'
|
||||||
|
: index === 1
|
||||||
? 'project-rbc'
|
? 'project-rbc'
|
||||||
: index === 4
|
: index === 2
|
||||||
? 'project-aws'
|
? 'project-aws'
|
||||||
: undefined,
|
: undefined,
|
||||||
status: index === 3
|
status: index === 1
|
||||||
? 'completed' as const
|
? 'completed' as const
|
||||||
: index === 5
|
: index === 3
|
||||||
? 'building' as const
|
? 'building' as const
|
||||||
: 'live' as const,
|
: 'live' as const,
|
||||||
media: index === 3
|
media: index === 1
|
||||||
? { type: 'image' as const, url: '/rbc-images.jpeg' }
|
? { type: 'image' as const, url: '/rbc-images.jpeg' }
|
||||||
: index === 4
|
: index === 2
|
||||||
? { type: 'video' as const, url: 'https://www.youtube.com/watch?v=wdJ5DN15jus' }
|
? { type: 'video' as const, url: 'https://www.youtube.com/watch?v=wdJ5DN15jus' }
|
||||||
: undefined,
|
: undefined,
|
||||||
videoUrl: index === 5 ? 'https://github.com/rockliang/go-flink' : '#',
|
videoUrl: index === 3 ? 'https://github.com/rockliang/go-flink' : '#',
|
||||||
articleUrl: index === 4 ? 'https://lnkd.in/p/gm2PZkWw' : '#',
|
articleUrl: index === 2 ? 'https://lnkd.in/p/gm2PZkWw' : '#',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const colors = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const skills = [
|
const skills = [
|
||||||
['LangGraph', 'Temporal', 'LLM Ops', 'PyTorch', 'Kafka', 'Observability', 'Go-Flink'],
|
['Kubernetes', 'Talos', 'ArgoCD', 'Terraform', 'Authentik', 'Prometheus', 'Grafana', 'vLLM', 'Temporal', 'Kafka', 'PostgreSQL', 'Go'],
|
||||||
['Golang', 'Terraform', 'IaC', 'OpenShift', 'Docker', 'Distributed Systems', 'Grafana'],
|
['Golang', 'Terraform', 'IaC', 'OpenShift', 'Docker', 'Distributed Systems', 'Grafana'],
|
||||||
['Java', 'AWS', 'DynamoDB', 'CloudWatch', 'gRPC', 'Distributed Systems', 'Ownership', 'Disaster Recovery', 'Observability'],
|
['Java', 'AWS', 'DynamoDB', 'CloudWatch', 'gRPC', 'Distributed Systems', 'Ownership', 'Disaster Recovery', 'Observability'],
|
||||||
['C++', 'CMake', 'Golang', 'Docker'],
|
['C++', 'CMake', 'Golang', 'Docker'],
|
||||||
@@ -20,7 +20,7 @@ const skills = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
null,
|
'#project-homelab', // Homelab - link to homelab project showcase
|
||||||
'#project-rbc', // RBC - link to RBC project showcase
|
'#project-rbc', // RBC - link to RBC project showcase
|
||||||
'#project-aws', // AWS - link to AWS project showcase
|
'#project-aws', // AWS - link to AWS project showcase
|
||||||
null,
|
null,
|
||||||
@@ -28,29 +28,19 @@ const links = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const linkTexts = [
|
const linkTexts = [
|
||||||
'',
|
'Learn more →',
|
||||||
'Learn more →',
|
'Learn more →',
|
||||||
'Learn more →',
|
'Learn more →',
|
||||||
'',
|
'',
|
||||||
'View Product →',
|
'View Product →',
|
||||||
]
|
]
|
||||||
|
|
||||||
const containerVariants = {
|
|
||||||
hidden: { opacity: 0 },
|
|
||||||
visible: {
|
|
||||||
opacity: 1,
|
|
||||||
transition: {
|
|
||||||
staggerChildren: 0.2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const itemVariants = {
|
const itemVariants = {
|
||||||
hidden: { opacity: 0, x: -20 },
|
hidden: { opacity: 0, y: 40 },
|
||||||
visible: {
|
visible: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
x: 0,
|
y: 0,
|
||||||
transition: { duration: 0.6, ease: 'easeOut' },
|
transition: { duration: 0.5, ease: 'easeOut' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,13 +63,7 @@ export function ExperienceTimeline() {
|
|||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<div className="relative">
|
||||||
variants={containerVariants}
|
|
||||||
initial="hidden"
|
|
||||||
whileInView="visible"
|
|
||||||
viewport={{ once: true }}
|
|
||||||
className="relative"
|
|
||||||
>
|
|
||||||
{/* Timeline line */}
|
{/* Timeline line */}
|
||||||
<div className="absolute left-0 md:left-1/2 top-0 bottom-0 w-1 bg-gradient-to-b from-blue-500 via-purple-500 to-green-500 md:transform md:-translate-x-1/2" />
|
<div className="absolute left-0 md:left-1/2 top-0 bottom-0 w-1 bg-gradient-to-b from-blue-500 via-purple-500 to-green-500 md:transform md:-translate-x-1/2" />
|
||||||
|
|
||||||
@@ -89,6 +73,9 @@ export function ExperienceTimeline() {
|
|||||||
<motion.div
|
<motion.div
|
||||||
key={item.company}
|
key={item.company}
|
||||||
variants={itemVariants}
|
variants={itemVariants}
|
||||||
|
initial="hidden"
|
||||||
|
whileInView="visible"
|
||||||
|
viewport={{ once: true, margin: "-100px" }}
|
||||||
className="relative pl-8 md:pl-0"
|
className="relative pl-8 md:pl-0"
|
||||||
>
|
>
|
||||||
{/* Timeline dot */}
|
{/* Timeline dot */}
|
||||||
@@ -138,7 +125,7 @@ export function ExperienceTimeline() {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-4
@@ -70,8 +70,21 @@ export default function Header() {
|
|||||||
{t.header.nav.skills}
|
{t.header.nav.skills}
|
||||||
</button>
|
</button>
|
||||||
{skillsOpen && (
|
{skillsOpen && (
|
||||||
<div className="absolute right-0 mt-2 w-48 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg p-4 space-y-2">
|
<div className="absolute right-0 mt-2 w-80 bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-lg shadow-lg p-4">
|
||||||
<div className="text-xs font-semibold text-gray-600 dark:text-gray-400">{t.header.skillsPlaceholder}</div>
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
{Object.entries(t.header.skills).map(([key, category]) => (
|
||||||
|
<div key={key}>
|
||||||
|
<div className="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-1">{(category as {title: string}).title}</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{(category as {items: string[]}).items.map((skill) => (
|
||||||
|
<span key={skill} className="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||||
|
{skill}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -234,8 +247,21 @@ export default function Header() {
|
|||||||
{t.header.nav.skills}
|
{t.header.nav.skills}
|
||||||
</button>
|
</button>
|
||||||
{skillsOpen && (
|
{skillsOpen && (
|
||||||
<div className="bg-gray-50 dark:bg-gray-800 rounded p-3 mt-2 space-y-2">
|
<div className="bg-gray-50 dark:bg-gray-800 rounded p-3 mt-2">
|
||||||
<div className="text-xs font-semibold text-gray-600 dark:text-gray-400">{t.header.skillsPlaceholder}</div>
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
{Object.entries(t.header.skills).map(([key, category]) => (
|
||||||
|
<div key={key}>
|
||||||
|
<div className="text-xs font-semibold text-gray-500 dark:text-gray-400 mb-1">{(category as {title: string}).title}</div>
|
||||||
|
<div className="flex flex-wrap gap-1">
|
||||||
|
{(category as {items: string[]}).items.map((skill) => (
|
||||||
|
<span key={skill} className="text-xs px-1.5 py-0.5 bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300 rounded">
|
||||||
|
{skill}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
|
|||||||
+90
-36
@@ -33,7 +33,32 @@
|
|||||||
"email": "Email",
|
"email": "Email",
|
||||||
"copy": "Copy"
|
"copy": "Copy"
|
||||||
},
|
},
|
||||||
"skillsPlaceholder": "Skills will appear here"
|
"skills": {
|
||||||
|
"infrastructure": {
|
||||||
|
"title": "Infrastructure",
|
||||||
|
"items": ["Kubernetes", "Talos", "ArgoCD", "Terraform", "Docker", "OpenShift"]
|
||||||
|
},
|
||||||
|
"cloud": {
|
||||||
|
"title": "Cloud & Distributed",
|
||||||
|
"items": ["AWS", "DynamoDB", "CloudWatch", "gRPC", "Cloudflare"]
|
||||||
|
},
|
||||||
|
"languages": {
|
||||||
|
"title": "Languages",
|
||||||
|
"items": ["Go", "Java", "Python", "C++", "TypeScript"]
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"title": "Data & Messaging",
|
||||||
|
"items": ["Kafka", "PostgreSQL", "Temporal", "Redis"]
|
||||||
|
},
|
||||||
|
"aiml": {
|
||||||
|
"title": "AI/ML",
|
||||||
|
"items": ["vLLM", "PyTorch", "Ollama", "KServe"]
|
||||||
|
},
|
||||||
|
"observability": {
|
||||||
|
"title": "Observability",
|
||||||
|
"items": ["Prometheus", "Grafana", "Loki", "OpenTelemetry"]
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
"title": "Senior Software Engineer",
|
"title": "Senior Software Engineer",
|
||||||
@@ -79,22 +104,24 @@
|
|||||||
"askPoimen": "Ask Poimen for technical details",
|
"askPoimen": "Ask Poimen for technical details",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"title": "Homelab: Kubernetes Cluster",
|
"title": "Homelab: Self-Hosted Cloud",
|
||||||
"description": "Bare-metal Kubernetes cluster running production workloads.",
|
"description": "Production-grade cloud platform rebuilt from scratch on bare-metal Kubernetes.",
|
||||||
"longDescription": "Talos OS with 3-node control plane. Cilium eBPF CNI, Longhorn storage, ArgoCD GitOps.",
|
"longDescription": "4-node Talos cluster (3 control plane + 1 worker) with OIDC SSO, GitOps (ArgoCD), CI/CD, Kafka, PostgreSQL, S3 storage, GPU LLM inference, and Temporal workflows.",
|
||||||
"stat": "3-node cluster, 99.2% uptime"
|
"stat": "4 nodes, 20+ services, 99.2% uptime",
|
||||||
},
|
"highlight": "AWS rebuilt at home—full stack from compute to observability.",
|
||||||
{
|
"bullets": [
|
||||||
"title": "Homelab: LLM Inference",
|
"Kubernetes (Talos Linux) + ArgoCD GitOps + Terraform IaC + Kustomize manifests",
|
||||||
"description": "Production LLM inference pipeline with CPU optimization.",
|
"Authentik OIDC SSO + RBAC + SOPS encrypted secrets + cert-manager TLS",
|
||||||
"longDescription": "Temporal-based orchestration with INT4/INT8 quantization. Achieved 60% latency reduction.",
|
"Longhorn block storage + MinIO S3 + CloudNativePG PostgreSQL + pgvector",
|
||||||
"stat": "60% latency cut, real-time inference"
|
"Prometheus + Grafana + Loki + Tempo + OpenTelemetry observability stack",
|
||||||
},
|
"vLLM GPU inference (Qwen3-32B) + Ollama + TEI embeddings + KServe orchestration",
|
||||||
{
|
"Temporal workflows + Kafka/Redpanda streaming + Forgejo CI/CD + DinD runners",
|
||||||
"title": "Homelab: Kafka Cluster",
|
"Go API gateway + Python ML + Next.js frontend + Cloudflare Tunnel zero-trust"
|
||||||
"description": "High-throughput event streaming with Strimzi KRaft.",
|
],
|
||||||
"longDescription": "3-broker KRaft cluster with auto-scaling, persistent storage, and monitoring.",
|
"deepDive": {
|
||||||
"stat": "3 brokers, 1K+ msgs/sec"
|
"label": "Deep Dive: Homelab Architecture →",
|
||||||
|
"url": "/homelab"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "RBC: Multi-Cloud Platform",
|
"title": "RBC: Multi-Cloud Platform",
|
||||||
@@ -177,7 +204,7 @@
|
|||||||
"helpText": "type 'help' for commands"
|
"helpText": "type 'help' for commands"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"copyright": "© 2025 Rock Liang. Deployed on homelab Kubernetes cluster (3-node Talos).",
|
"copyright": "© 2025 Rock Liang. Deployed on homelab Kubernetes cluster (4-node Talos).",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
"email": "Email"
|
"email": "Email"
|
||||||
}
|
}
|
||||||
@@ -216,7 +243,32 @@
|
|||||||
"email": "邮箱",
|
"email": "邮箱",
|
||||||
"copy": "复制"
|
"copy": "复制"
|
||||||
},
|
},
|
||||||
"skillsPlaceholder": "技能将显示在这里"
|
"skills": {
|
||||||
|
"infrastructure": {
|
||||||
|
"title": "基础设施",
|
||||||
|
"items": ["Kubernetes", "Talos", "ArgoCD", "Terraform", "Docker", "OpenShift"]
|
||||||
|
},
|
||||||
|
"cloud": {
|
||||||
|
"title": "云 & 分布式",
|
||||||
|
"items": ["AWS", "DynamoDB", "CloudWatch", "gRPC", "Cloudflare"]
|
||||||
|
},
|
||||||
|
"languages": {
|
||||||
|
"title": "编程语言",
|
||||||
|
"items": ["Go", "Java", "Python", "C++", "TypeScript"]
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"title": "数据 & 消息",
|
||||||
|
"items": ["Kafka", "PostgreSQL", "Temporal", "Redis"]
|
||||||
|
},
|
||||||
|
"aiml": {
|
||||||
|
"title": "AI/ML",
|
||||||
|
"items": ["vLLM", "PyTorch", "Ollama", "KServe"]
|
||||||
|
},
|
||||||
|
"observability": {
|
||||||
|
"title": "可观测性",
|
||||||
|
"items": ["Prometheus", "Grafana", "Loki", "OpenTelemetry"]
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"hero": {
|
"hero": {
|
||||||
"title": "高级软件工程师",
|
"title": "高级软件工程师",
|
||||||
@@ -262,22 +314,24 @@
|
|||||||
"askPoimen": "问Poimen了解技术细节",
|
"askPoimen": "问Poimen了解技术细节",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"title": "家庭实验室:Kubernetes集群",
|
"title": "家庭实验室:自托管云",
|
||||||
"description": "运行生产工作负载的裸机Kubernetes集群。",
|
"description": "在裸机Kubernetes上从零构建的生产级云平台。",
|
||||||
"longDescription": "Talos OS 3节点控制平面,Cilium eBPF CNI,Longhorn存储,ArgoCD GitOps。",
|
"longDescription": "4节点Talos集群(3控制平面+1工作节点),具备OIDC SSO、GitOps (ArgoCD)、CI/CD、Kafka、PostgreSQL、S3存储、GPU LLM推理、Temporal工作流。",
|
||||||
"stat": "3节点集群,99.2%可用性"
|
"stat": "4节点,20+服务,99.2%可用性",
|
||||||
},
|
"highlight": "在家重建AWS——从计算到可观测性的完整栈。",
|
||||||
{
|
"bullets": [
|
||||||
"title": "家庭实验室:LLM推理",
|
"Kubernetes (Talos Linux) + ArgoCD GitOps + Terraform IaC + Kustomize",
|
||||||
"description": "具有CPU优化的生产LLM推理管道。",
|
"Authentik OIDC SSO + RBAC + SOPS加密密钥 + cert-manager TLS",
|
||||||
"longDescription": "基于Temporal的编排,INT4/INT8量化,延迟降低60%。",
|
"Longhorn块存储 + MinIO S3 + CloudNativePG PostgreSQL + pgvector",
|
||||||
"stat": "延迟降低60%,实时推理"
|
"Prometheus + Grafana + Loki + Tempo + OpenTelemetry可观测性栈",
|
||||||
},
|
"vLLM GPU推理 (Qwen3-32B) + Ollama + TEI嵌入 + KServe编排",
|
||||||
{
|
"Temporal工作流 + Kafka/Redpanda流处理 + Forgejo CI/CD + DinD运行器",
|
||||||
"title": "家庭实验室:Kafka集群",
|
"Go API网关 + Python ML + Next.js前端 + Cloudflare Tunnel零信任"
|
||||||
"description": "使用Strimzi KRaft的高吞吐量事件流。",
|
],
|
||||||
"longDescription": "3代理KRaft集群,具有自动扩展、持久存储和监控。",
|
"deepDive": {
|
||||||
"stat": "3代理,1K+消息/秒"
|
"label": "深入了解:家庭实验室架构 →",
|
||||||
|
"url": "/homelab"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "RBC: 多云平台",
|
"title": "RBC: 多云平台",
|
||||||
@@ -360,7 +414,7 @@
|
|||||||
"helpText": "输入 'help' 查看命令"
|
"helpText": "输入 'help' 查看命令"
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"copyright": "© 2025 梁伟哲。部署于家庭实验室Kubernetes集群(3节点Talos)。",
|
"copyright": "© 2025 梁伟哲。部署于家庭实验室Kubernetes集群(4节点Talos)。",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
"email": "邮箱"
|
"email": "邮箱"
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user