diff --git a/src/app/homelab/page.tsx b/src/app/homelab/page.tsx index beb3e0ea..fbb7ad0f 100644 --- a/src/app/homelab/page.tsx +++ b/src/app/homelab/page.tsx @@ -74,6 +74,11 @@ const adrs = [ "act_runner v0.3.1 on Gitea LXC (10.99.0.22). Push to dev → node:22-alpine container builds Next.js → rsync out/ to Portfolio LXC → SSH docker rebuild.", why: "Keeps the full pipeline internal — no GitHub Actions, no external runners. Build runs in an isolated Alpine container so the Gitea LXC isn't polluted. Portfolio LXC (10.99.0.23) just serves pre-built static files via nginx.", }, + { + title: "Authentik over Authelia", + decision: "Authentik as the SSO provider across all self-hosted services.", + why: "Full OIDC provider + forward auth in one. Lets services like Outline, Gitea, and Vikunja use real SSO rather than just a login gate. Authelia is forward-auth only — no OIDC provider capability.", + }, ]; export default function HomelabPage() { diff --git a/src/components/Hero.tsx b/src/components/Hero.tsx index caba923c..caac4fcc 100644 --- a/src/components/Hero.tsx +++ b/src/components/Hero.tsx @@ -8,15 +8,15 @@ export default function Hero() { tyler koenig
- SOC Helpdesk I · Homelab Operator + Security Operations · Self-Hosted Infrastructure
- I write software and run infrastructure that goes well past what my - job title implies. Games, AI tooling, mobile apps, and a homelab - running 20+ self-hosted services on segmented VLANs. Continuously - learning by building things that actually work.{' '} + Security operations and self-hosted infrastructure. Homelab runs 37 + services across segmented VLANs — pfSense, Authentik SSO, full + observability stack. Write software too: mobile apps, Go backends, + open protocols. Daily drivers, all of it.{' '}
diff --git a/src/components/ProjectCard.tsx b/src/components/ProjectCard.tsx index 51958be2..53d21669 100644 --- a/src/components/ProjectCard.tsx +++ b/src/components/ProjectCard.tsx @@ -22,6 +22,17 @@ export default function ProjectCard({ project }: Props) { {project.stats} )} + {project.externalUrl && ( + + ↗ + + )} + {project.statusBadge && ( + + {project.statusBadge} + + )} +{project.description}
diff --git a/src/components/Skills.tsx b/src/components/Skills.tsx index 02cf6b25..530464cd 100644 --- a/src/components/Skills.tsx +++ b/src/components/Skills.tsx @@ -3,7 +3,7 @@ import Widget from "@/components/Widget"; const skillGroups = [ { label: "Languages", - skills: ["JavaScript", "TypeScript", "HTML", "CSS"], + skills: ["Go", "JavaScript", "TypeScript", "HTML", "CSS"], }, { label: "Frontend", diff --git a/src/data/projects.ts b/src/data/projects.ts index 8d6e3192..83fdc4cf 100644 --- a/src/data/projects.ts +++ b/src/data/projects.ts @@ -7,6 +7,8 @@ export type Project = { tier: "featured" | "archive"; stats?: string; year?: number; + statusBadge?: string; + externalUrl?: string; }; export const projects: Project[] = [ @@ -20,6 +22,8 @@ export const projects: Project[] = [ githubUrl: "https://github.com/lerko96/golf-book-mobile", tier: "featured", stats: "211 commits", + statusBadge: "Pending App Store Approval", + externalUrl: "#", }, { slug: "plaiground", @@ -28,7 +32,8 @@ export const projects: Project[] = [ "Cross-platform desktop AI chat app for developers. Supports OpenAI, Anthropic Claude, and Google Gemini in a single interface with real-time cost tracking, conversation export, and automatic code explanation.", tags: ["Electron", "Node.js", "OpenAI", "Claude", "Gemini"], githubUrl: "https://github.com/lerko96/plaiground", - tier: "featured", + tier: "archive", + year: 2025, }, { slug: "service-monitor", @@ -37,7 +42,8 @@ export const projects: Project[] = [ "Web dashboard for tracking uptime across multiple services with 30-second polling, status history visualization, JWT-authenticated API, and Docker + nginx deployment.", tags: ["React 18", "Vite", "Express", "SQLite", "Docker", "JWT"], githubUrl: "https://github.com/lerko96/service-monitor", - tier: "featured", + tier: "archive", + year: 2025, }, { slug: "tht-1.2", @@ -46,10 +52,40 @@ export const projects: Project[] = [ "3D visualization platform for exploring and organizing thoughts using a radio-tuning metaphor. Filter ideas by frequency and bandwidth in an instanced Three.js scene with persistent local storage.", tags: ["React", "TypeScript", "Three.js", "React Three Fiber", "Zustand"], githubUrl: "https://github.com/lerko96/tht-1.2", + tier: "archive", + year: 2025, + }, + + { + slug: "open-pact", + title: "open-pact", + description: + "Open protocol for AI agent identity, delegation, and portable memory. Ed25519 keypair identity, signed delegation warrants, portable signed memory facts. No central registry.", + tags: ["TypeScript", "Ed25519", "DID", "npm", "CC0"], + githubUrl: "https://github.com/lerko96/open-pact", + tier: "featured", + }, + { + slug: "helm", + title: "helm", + description: + "Full-stack personal productivity dashboard. Go backend with chi router and SQLite, React + TypeScript frontend. Notes, todos, calendar (CalDAV), clipboard, bookmarks, memos. Self-hosted, single-user, daily use.", + tags: ["Go", "React", "TypeScript", "SQLite", "CalDAV"], + githubUrl: "https://github.com/lerko96/helm", tier: "featured", }, // --- Archive --- + { + slug: "risk-ops", + title: "risk-ops", + description: + "Browser-based strategy dashboard for Risk: Global Domination (SMG Studio). Open one HTML file — no install needed.", + tags: ["HTML", "JavaScript"], + githubUrl: "#", + tier: "archive", + year: 2026, + }, { slug: "twitter-thread-ext", title: "twitter-thread-ext", diff --git a/src/data/services.ts b/src/data/services.ts index 76d8d2e4..d3c206f6 100644 --- a/src/data/services.ts +++ b/src/data/services.ts @@ -7,10 +7,13 @@ export type Service = { export const services: Service[] = [ // Infrastructure - { name: "pfSense", description: "Firewall, DHCP, routing, WireGuard VPN", category: "infrastructure", icon: "fas fa-shield-halved" }, + { name: "pfSense", description: "Firewall, DHCP, routing gateway on N100", category: "infrastructure", icon: "fas fa-shield-halved" }, { name: "Caddy", description: "Reverse proxy with automatic wildcard TLS via Cloudflare DNS-01", category: "infrastructure", icon: "fas fa-globe" }, { name: "Pi-hole", description: "Network-wide DNS + ad blocking", category: "infrastructure", icon: "fas fa-filter" }, - { name: "WireGuard", description: "VPN — 600–900 Mbps on N100, full LAN access for clients", category: "infrastructure", icon: "fas fa-lock" }, + { name: "WireGuard", description: "VPN — full LAN access for remote clients", category: "infrastructure", icon: "fas fa-lock" }, + { name: "mail relay", description: "Outbound SMTP relay for self-hosted service notifications", category: "infrastructure", icon: "fas fa-envelope" }, + { name: "gluetun", description: "VPN container routing download client traffic", category: "infrastructure", icon: "fas fa-shield" }, + { name: "Home Assistant", description: "Smart home automation and device management", category: "infrastructure", icon: "fas fa-house" }, // Security / Auth { name: "Authentik", description: "SSO provider — OIDC + forward auth across all services", category: "security", icon: "fas fa-id-badge" }, @@ -34,11 +37,21 @@ export const services: Service[] = [ { name: "Traggo", description: "Time tracking", category: "productivity", icon: "fas fa-clock" }, { name: "Baikal", description: "CalDAV / CardDAV server", category: "productivity", icon: "fas fa-calendar" }, { name: "Grist", description: "Spreadsheets and structured data", category: "productivity", icon: "fas fa-table" }, + { name: "Glance", description: "Self-hosted start page with feeds and service status", category: "productivity", icon: "fas fa-gauge" }, + { name: "Filebrowser", description: "Web-based file manager", category: "productivity", icon: "fas fa-folder-open" }, // Media - { name: "Plex + Jellyfin", description: "Media streaming", category: "media", icon: "fas fa-film" }, - { name: "Sonarr / Radarr / Lidarr", description: "Automated media management", category: "media", icon: "fas fa-download" }, - { name: "Calibre-Web", description: "Book library with auto-ingest", category: "media", icon: "fas fa-book-open" }, + { name: "Plex", description: "Media streaming — movies, TV, music", category: "media", icon: "fas fa-film" }, + { name: "Jellyfin", description: "Open-source media streaming", category: "media", icon: "fas fa-play" }, + { name: "Sonarr", description: "Automated TV show management", category: "media", icon: "fas fa-tv" }, + { name: "Radarr", description: "Automated movie management", category: "media", icon: "fas fa-video" }, + { name: "Lidarr", description: "Automated music management", category: "media", icon: "fas fa-music" }, + { name: "Prowlarr", description: "Indexer manager and proxy for the *arr stack", category: "media", icon: "fas fa-magnifying-glass" }, + { name: "Bazarr", description: "Automatic subtitle download and management", category: "media", icon: "fas fa-closed-captioning" }, + { name: "nzbget", description: "Usenet downloader", category: "media", icon: "fas fa-download" }, + { name: "qBittorrent", description: "Torrent client with web UI", category: "media", icon: "fas fa-magnet" }, + { name: "Kavita", description: "Self-hosted manga and book reader", category: "media", icon: "fas fa-book-open" }, + { name: "Openshelf", description: "Book library with auto-ingest", category: "media", icon: "fas fa-book-open" }, ]; export const categoryOrder: Service["category"][] = [ diff --git a/src/data/timeline.ts b/src/data/timeline.ts index 36717525..98466699 100644 --- a/src/data/timeline.ts +++ b/src/data/timeline.ts @@ -41,7 +41,7 @@ export const timeline: TimelineEntry[] = [ date: 'ongoing', title: 'Homelab — Proxmox Cluster', type: 'homelab', - description: '8-VLAN segmented network, Proxmox VMs/LXCs, SSO via Authentik, full monitoring stack (Grafana + Prometheus + Loki).', + description: '8-VLAN segmented network, Proxmox VMs/LXCs, SSO via Authentik, full monitoring stack (VictoriaMetrics + Grafana + Beszel + ntfy).', tags: ['proxmox', 'networking', 'monitoring', 'sso'], }, {