diff --git a/src/app/archive/page.tsx b/src/app/archive/page.tsx index 7ea1aadd..a1d9073d 100644 --- a/src/app/archive/page.tsx +++ b/src/app/archive/page.tsx @@ -1,68 +1,24 @@ -import type { Metadata } from "next"; -import Widget from "@/components/Widget"; -import { archiveProjects } from "@/data/projects"; +"use client"; -export const metadata: Metadata = { - title: "Archive | Tyler Koenig", - description: "Earlier projects and experiments — browser extensions, canvas apps, and bootcamp work.", -}; +import { useEffect } from "react"; + +export default function ArchiveRedirect() { + useEffect(() => { + window.location.replace("/projects/"); + }, []); -export default function ArchivePage() { return ( <> -
-

- - tyler/projects/archive -

-

- Experiments, browser extensions, and bootcamp projects. Kept here for context — not - representative of current work. -

-
- - -
- {archiveProjects.map((project) => ( - -
-
- {project.year && ( - - {project.year} - - )} - - {project.title} - -
-

- {project.description} -

-
- {project.tags.map((tag) => ( - - {tag} - - ))} -
-
- -
- ))} -
-
+ +

+ This page moved.{" "} + + /projects/ + +

); } diff --git a/src/app/homelab/page.tsx b/src/app/homelab/page.tsx index ea8348a6..870396c5 100644 --- a/src/app/homelab/page.tsx +++ b/src/app/homelab/page.tsx @@ -22,98 +22,90 @@ const glanceStats = [ const vlans = [ { - id: "1000", + id: "MGMT", name: "MGMT", - subnet: "10.0.0.0/24", purpose: "Network equipment only", }, { - id: "1010", + id: "LAN", name: "LAN", - subnet: "10.1.0.0/24", purpose: "Trusted personal devices", }, { - id: "1020", + id: "Lab", name: "Homelab", - subnet: "10.2.0.0/24", purpose: "All self-hosted services", }, { - id: "1030", + id: "Guest", name: "Guests", - subnet: "10.3.0.0/24", purpose: "Internet only, RFC1918 blocked", }, { - id: "1040", + id: "IoT", name: "IoT", - subnet: "10.4.0.0/24", purpose: "Smart home, isolated", }, { - id: "1050", + id: "WFH", name: "WFH", - subnet: "10.5.0.0/24", purpose: "Work devices, no personal access", }, { - id: "1099", + id: "DMZ", name: "DMZ", - subnet: "10.99.0.0/24", purpose: "Public-facing, hard-blocked internally", }, { id: "VPN", name: "VPN", - subnet: "10.200.0.0/24", - purpose: "WireGuard clients = LAN access", + purpose: "WireGuard clients, LAN-equivalent access", }, ]; const adrs = [ { - title: "AT&T Gateway: IP Passthrough over EAP bypass", + title: "ISP gateway: passthrough mode", decision: - "BGW320 stays in-line with IP Passthrough mode. pfSense gets the public IP directly. Gateway WiFi disabled.", - why: "AT&T locks 802.1X auth to their gateway hardware. EAP proxy bypass is brittle — breaks on firmware updates and only saves 1–2ms. True bridge mode isn't supported.", + "ISP gateway stays in-line in passthrough mode, pfSense gets the public IP directly. Gateway WiFi disabled.", + why: "Carrier locks 802.1X auth to their own gateway hardware, and bypassing it is brittle — breaks on firmware updates and only saves a millisecond or two. True bridge mode isn't supported. Passthrough is the cleanest option that keeps pfSense as the actual perimeter.", }, { title: "Caddy over NGINX Proxy Manager", decision: - "Caddy with DNS-01 challenge via Cloudflare API. All subdomains resolve to Caddy internally via Pi-hole. Caddy terminates SSL and proxies to backends.", - why: "Single Caddyfile, auto-cert without exposing port 80/443 to the internet. NPM has more UI overhead for the same outcome. Traefik is more complex for no benefit here.", + "Caddy with DNS-01 challenge via Cloudflare API. All subdomains resolve to Caddy internally via Pi-hole. Caddy terminates TLS and proxies to backends.", + why: "Single Caddyfile, automatic certs without ever needing to expose internal services to the internet for an HTTP-01 challenge. NPM has more UI overhead for the same outcome. Traefik is more complex for no benefit at this scale.", }, { title: "WireGuard over OpenVPN", decision: - "WireGuard on pfSense, UDP 51820, VPN subnet 10.200.0.0/24. Clients get LAN + MGMT access, blocked from Guest/IoT/WFH.", - why: "Faster, simpler config, better battery life on mobile. ~600–900 Mbps on an N100. OpenVPN has no advantage here. Tailscale adds an external relay dependency.", + "WireGuard on pfSense as the only remote-access path. Clients get the access tier documented in the access model — same as LAN, plus the admin surfaces that aren't reachable any other way.", + why: "Faster, simpler config, better battery life on mobile. Throughput on the firewall hardware comfortably exceeds the WAN link. OpenVPN has no advantage here. Tailscale would add an external relay dependency for a problem WireGuard already solves.", }, { title: "Pi-hole in Homelab VLAN, not MGMT", decision: - "Pi-hole at 10.2.0.11 (VLAN 1020). Firewall allows port 53 inbound from all VLANs. MGMT uses pfSense Unbound as its primary DNS.", - why: "Putting Pi-hole in MGMT would require opening MGMT to all VLANs — a larger attack surface. DNS traffic crossing into Homelab VLAN is the lesser risk.", + "Pi-hole runs in the Homelab VLAN. Firewall allows port 53 inbound from VLANs that need local resolution. MGMT uses pfSense Unbound as its primary resolver instead.", + why: "Putting Pi-hole in MGMT would mean opening MGMT to all the VLANs that need DNS — much bigger attack surface for the most sensitive tier. DNS traffic crossing into the Homelab VLAN is the lesser risk, and Homelab is already where service traffic terminates anyway.", }, { - title: "N100 for pfSense", + title: "Mini-PC for pfSense", decision: - "Intel N100 mini PC: 4-core 3.4 GHz, ~6W idle. Handles 2–3 Gbps routing and 600–900 Mbps WireGuard.", - why: "Right-sized for 1 Gbps fiber with headroom. Raspberry Pi can't handle 1 Gbps + VPN. A full rack server wastes power for this role.", + "Intel N100 mini-PC as the firewall host. ~6W idle, handles multi-Gbps routing, saturates the WAN link with WireGuard headroom to spare.", + why: "Right-sized for 1 Gbps fiber. A Raspberry Pi can't handle 1 Gbps plus VPN. A full rack server wastes power for this role and adds noise to a room I sit in.", }, { title: "Shared Postgres + Redis in apps LXC", decision: - "One Postgres instance, multiple databases. One Redis instance. A single init script provisions all schemas on first run.", - why: "Avoids 15 separate DB containers. Reduces RAM overhead significantly. All productivity apps share the same LXC (10.2.0.60).", + "One Postgres instance hosting multiple databases. One Redis instance. A single init script provisions schemas on first run.", + why: "Avoids ~15 separate DB containers. Big RAM savings. Productivity apps colocate in one LXC anyway, so a shared backing store there is the natural shape.", }, { title: - "Gitea CI/CD: Self-hosted runner with container build + SSH rsync deploy", + "Gitea CI/CD: self-hosted runner, internal pipeline, static deploy", decision: - "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.", + "Self-hosted Gitea Actions runner builds the portfolio on push, then deploys pre-built static files to the public-facing host. Build runs in an isolated container so the runner host stays clean. Public host serves static files only — no build toolchain on it.", + why: "Keeps the whole pipeline internal. No external runners, no GitHub Actions. The build/serve split means the public-facing host has the smallest possible footprint — static file server, nothing more.", }, { title: "Authentik over Authelia", @@ -163,7 +155,7 @@ export default function HomelabPage() { {/* VLAN table */}
@@ -171,14 +163,11 @@ export default function HomelabPage() { - VLAN + Segment Name - - Subnet - Purpose @@ -196,9 +185,6 @@ export default function HomelabPage() { {v.name} - - {v.subnet} - {v.purpose} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 78c8721c..e937b125 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -24,7 +24,7 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - + diff --git a/src/app/page.tsx b/src/app/page.tsx index 8b4630b7..4d6c08b3 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,10 +1,6 @@ import type { Metadata } from "next"; import Hero from "@/components/Hero"; -import Skills from "@/components/Skills"; import Timeline from "@/components/Timeline"; -import ProjectCard from "@/components/ProjectCard"; -import Widget from "@/components/Widget"; -import { featuredProjects } from "@/data/projects"; export const metadata: Metadata = { title: "Tyler Koenig", @@ -17,14 +13,6 @@ export default function Home() { <> - -
- {featuredProjects.map((project) => ( - - ))} -
-
- ); } diff --git a/src/app/projects/page.tsx b/src/app/projects/page.tsx new file mode 100644 index 00000000..3a272714 --- /dev/null +++ b/src/app/projects/page.tsx @@ -0,0 +1,77 @@ +import type { Metadata } from "next"; +import Widget from "@/components/Widget"; +import ProjectCard from "@/components/ProjectCard"; +import { featuredProjects, archiveProjects } from "@/data/projects"; + +export const metadata: Metadata = { + title: "Projects | Tyler Koenig", + description: + "Featured projects and earlier work — homelab, open-pact, helm, and bootcamp/experiment archive.", +}; + +export default function ProjectsPage() { + return ( + <> +
+

+ + projects +

+

+ Featured work first. Earlier experiments, browser extensions, and bootcamp projects below — kept for context. +

+
+ + +
+ {featuredProjects.map((project) => ( + + ))} +
+
+ + + + + + ); +} diff --git a/src/components/Nav.tsx b/src/components/Nav.tsx index c420c2fc..388bf99d 100644 --- a/src/components/Nav.tsx +++ b/src/components/Nav.tsx @@ -7,7 +7,7 @@ import { useTheme } from "@/context/ThemeContext"; const links = [ { href: "/", label: "tyler" }, { href: "/homelab/", label: "homelab" }, - { href: "/archive/", label: "archive" }, + { href: "/projects/", label: "projects" }, ]; export default function Nav() { diff --git a/src/data/projects.ts b/src/data/projects.ts index af6fc7b2..de31304d 100644 --- a/src/data/projects.ts +++ b/src/data/projects.ts @@ -17,22 +17,12 @@ export const projects: Project[] = [ slug: "homelab", title: "homelab", description: - "8-VLAN segmented network, Proxmox VMs/LXCs, SSO via Authentik, full monitoring stack (VictoriaMetrics + Grafana + Beszel + ntfy).", + "7-VLAN segmented network, Wireguard VPN, Proxmox VMs/LXCs, SSO via Authentik, full monitoring stack (VictoriaMetrics + Grafana + Beszel + ntfy).", tags: ["Markdown", "Mermaid", "Proxmox", "Monitor", "Backup"], githubUrl: "https://gitea.lerkolabs.com/lerko/homelab", tier: "featured", year: 2026, }, - { - 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", - year: 2026, - }, { slug: "portfolio", title: "portfolio", @@ -43,6 +33,27 @@ export const projects: Project[] = [ tier: "featured", year: 2021, }, + { + slug: "nib", + title: "nib", + description: + "Capture-first personal journal built with Go + React + SQLite. Currently developing in private when I have spare time.", + tags: ["Go", "React", "SQLite", "Journal", "Stream-of-Thought"], + githubUrl: "https://github.com/lerko96/nib", + tier: "featured", + year: 2026, + }, + { + 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", + year: 2026, + }, + // --- Archive --- { slug: "helm", title: "helm", @@ -50,20 +61,19 @@ export const projects: Project[] = [ "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", + tier: "archive", year: 2026, }, { - slug: "claude-vault", - title: "claude-vault", + slug: "risk-ops", + title: "risk-ops", description: - "A scaffolding system for maintaining a living project knowledge base alongside a code repo, powered by Claude Code skills.", - tags: ["Shell", "Developer-Tools", "Claude", "Knowledge-Management"], - githubUrl: "https://github.com/lerko96/claude-vault", - tier: "featured", + "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, }, - // --- Archive --- { slug: "golf-book-mobile", title: "golf-book-mobile", @@ -76,16 +86,6 @@ export const projects: Project[] = [ statusBadge: "Pending App Store Approval", year: 2025, }, - { - 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/timeline.ts b/src/data/timeline.ts index c0966a55..d79a26de 100644 --- a/src/data/timeline.ts +++ b/src/data/timeline.ts @@ -47,12 +47,19 @@ export const timeline: TimelineEntry[] = [ tags: ["go", "react", "typescript"], }, { - date: "2024-08", + date: "2025", + title: "Proxmox Backup Server", + type: "homelab", + description: "Deployed PBS on used desktop hardware for disaster recovery.", + tags: ["backup", "recovery", "retention"], + }, + { + date: "2025", title: "Proxmox Cluster", type: "homelab", description: - "Proxmox VMs/LXCs, SSO via Authentik, full monitoring stack (VictoriaMetrics + Grafana + Beszel + ntfy).", - tags: ["proxmox", "networking", "monitoring", "sso"], + "Proxmox installed on dedicated server and the fun begins. VMs/LXCs, SSO via Authentik, full monitoring stack (VictoriaMetrics + Grafana + Beszel + ntfy).", + tags: ["proxmox", "containers", "VMs", "linux"], }, { date: "2024-06", @@ -66,7 +73,8 @@ export const timeline: TimelineEntry[] = [ date: "2024-03", title: "pfSense", type: "homelab", - description: "Netgate pfSense n100 picked up on ebay.", + description: + "Netgate 1100 picked up on ebay to experience hands on networking configuration and troubleshooting.", tags: ["network", "firewall", "vlan", "dhcp"], }, {