Snapshot: full project state
This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
node_modules/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
.env
|
||||||
|
*.db
|
||||||
|
*.sqlite*
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
out/
|
||||||
|
work/
|
||||||
|
.pio/
|
||||||
|
briefs/
|
||||||
|
dns-backup/
|
||||||
|
archive/
|
||||||
|
*.png
|
||||||
10
README.md
Normal file
10
README.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# Tech Skill Monetization
|
||||||
|
|
||||||
|
Freelance "Indiana Holmes" offering kit: positioning docs, platform research,
|
||||||
|
outreach templates, the portfolio site, and the Stripe dunning side-app.
|
||||||
|
|
||||||
|
- `positioning.md` — one-line pitch + long bio (local-AI-on-your-hardware)
|
||||||
|
- `platforms.md` / `outreach.md` — where and how to sell
|
||||||
|
- `portfolio/`, `portfolio-site/` — portfolio.thetempleofdoom.com
|
||||||
|
- `stripe-dunning-app/` — failed-payment recovery tool
|
||||||
|
|
||||||
93
outreach.md
Normal file
93
outreach.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# Outreach — Live Targets + Drafted Messages + Tracking Log
|
||||||
|
|
||||||
|
## Method
|
||||||
|
|
||||||
|
Find real people stuck on the exact problem I've solved (Ollama/GPU passthrough / local
|
||||||
|
LLM infra), and offer a specific fix — never a template. Reddit help threads first (zero
|
||||||
|
cost, genuine need), then Upwork job posts.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Live targets (found, verified)
|
||||||
|
|
||||||
|
### Reddit threads — "Ollama won't use GPU" (direct fit)
|
||||||
|
|
||||||
|
1. **r/ollama — "No GPU utilization"**
|
||||||
|
`https://www.reddit.com/r/ollama/comments/1t4srec/no_gpu_utilization/`
|
||||||
|
→ The user's GPU isn't being used. First step: `ollama ps` shows PROCESSOR=CPU.
|
||||||
|
|
||||||
|
2. **r/LocalLLaMA — "Ollama not using GPU, need help"**
|
||||||
|
`https://www.reddit.com/r/LocalLLaMA/comments/1jw5m8k/ollama_not_using_gpu_need_help/`
|
||||||
|
|
||||||
|
3. **r/LocalLLaMA — "Proxmox and LXC Passthrough for Ollama Best Practices?"**
|
||||||
|
`https://www.reddit.com/r/LocalLLaMA/comments/1irmk5n/proxmox_and_lxc_passthrough_for_ollama_best/`
|
||||||
|
→ Direct passthrough-in-LXC question. My exact lane.
|
||||||
|
|
||||||
|
4. **r/LocalLLaMA — "Need help setting up a local LLM server with RTX 3060"**
|
||||||
|
`https://www.reddit.com/r/LocalLLaMA/comments/1lvm3kv/need_help_setting_up_a_local_llm_server_with_rtx/`
|
||||||
|
|
||||||
|
### Upwork — Proxmox/AI infra (hire intent)
|
||||||
|
|
||||||
|
5. Upwork "Proxmox VE Specialists for Hire" landing page → browse open GPU passthrough /
|
||||||
|
homelab / self-hosted AI jobs, apply only to the specific-fit ones.
|
||||||
|
`https://www.upwork.com/hire/proxmox-ve-freelancers/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Drafted messages (specific, non-templated — reference THEIR problem)
|
||||||
|
|
||||||
|
### Target 1 — r/ollama "No GPU utilization"
|
||||||
|
|
||||||
|
> Before you reinstall anything — run `ollama ps` while a model is loaded and check the
|
||||||
|
> PROCESSOR column. If it says CPU, that's the real signal. Then `nvidia-smi` in a second
|
||||||
|
> terminal while it's inferring: if Ollama doesn't appear as a GPU process, the driver
|
||||||
|
> isn't the issue — the model is too big for VRAM and it's spilling to CPU, or you're on
|
||||||
|
> a card where CUDA silently fell back to Vulkan (I hit exactly this on a Quadro M4000
|
||||||
|
> after Ollama 0.34 dropped Maxwell CUDA). What GPU + what model are you running? I can
|
||||||
|
> tell you which it is from those two commands.
|
||||||
|
|
||||||
|
*(Value: gives the diagnostic, demonstrates the exact non-obvious Maxwell/Vulkan fix.)*
|
||||||
|
|
||||||
|
### Target 3 — r/LocalLLaMA "Proxmox and LXC Passthrough for Ollama"
|
||||||
|
|
||||||
|
> The LXC vs full VM passthrough question comes down to whether you need it exposed to
|
||||||
|
> ONE container or shared. For a single always-on Ollama container, device passthrough
|
||||||
|
> (`/dev/nvidia*`) + `nvidia-container-toolkit` in the LXC beats full VM passthrough —
|
||||||
|
> you keep snapshot/backup and don't lose the card to a VM. I run a 4-node setup (4080
|
||||||
|
> CUDA lane + 3070 batch lane + M4000 Vulkan always-on lane + MacBook voice) — happy to
|
||||||
|
> share the exact systemd/env config if you say which card you're passing through.
|
||||||
|
|
||||||
|
### Target 4 — r/LocalLLaMA "RTX 3060 local LLM server"
|
||||||
|
|
||||||
|
> 3060 = 12GB, that's a solid self-host box. The trap is default Ollama will spill a
|
||||||
|
> 13B+ model to CPU and you'll think it's broken. Pin a 7–9B Q4 model with `num_gpu 99`
|
||||||
|
> and `OLLAMA_KV_CACHE_TYPE q4_0` and you'll stay 100% GPU. I did 100K context on 16GB
|
||||||
|
> with zero offload using Flash Attention + KV quant — the 3060 will do ~24–32K ctx
|
||||||
|
> comfortably. Want the exact override.conf I use?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tracking log
|
||||||
|
|
||||||
|
| # | Platform | Target | Status | Follow-up date |
|
||||||
|
|---|----------|--------|--------|----------------|
|
||||||
|
| 1 | Reddit | r/ollama "No GPU utilization" | ⬜ draft ready | — |
|
||||||
|
| 2 | Reddit | r/LocalLLaMA "not using GPU" | ⬜ draft ready | — |
|
||||||
|
| 3 | Reddit | r/LocalLLaMA "LXC passthrough" | ⬜ draft ready | — |
|
||||||
|
| 4 | Reddit | r/LocalLLaMA "RTX 3060 server" | ⬜ draft ready | — |
|
||||||
|
| 5 | Upwork | Proxmox/AI infra jobs | ⬜ browse + apply | — |
|
||||||
|
|
||||||
|
**Rules:**
|
||||||
|
- Quality over volume — 5 targeted > 50 generic.
|
||||||
|
- No unpaid "exposure" work. A quick free *diagnostic* (2 commands) is fine — it earns
|
||||||
|
trust; a free *build* is not.
|
||||||
|
- Send from YOUR real Reddit account, human-paced (max 1–2/day at first). Mass DMs from a
|
||||||
|
fresh account = shadowban.
|
||||||
|
- Log every response in this table (status: ⬜ sent / 🟡 replied / ✅ gig / ❌ dead).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Content flywheel (optional, after first win)
|
||||||
|
|
||||||
|
Turn the R630 GPU→Ollama build into a YouTube/blog tutorial, cross-linking the Gumroad
|
||||||
|
guide + Upwork profile. The Maxwell/Vulkan gotcha is genuinely novel enough to rank.
|
||||||
116
platforms.md
Normal file
116
platforms.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# Platform Setup — Profiles, Rates, Checklist
|
||||||
|
|
||||||
|
## Target platforms (3, not spread thin)
|
||||||
|
|
||||||
|
1. **Upwork** — highest-intent freelance gigs (Proxmox/AI infra buyers actively hire here)
|
||||||
|
2. **Reddit** — reputation + inbound (r/homelab, r/selfhosted, r/LocalLLaMA, r/ollama)
|
||||||
|
3. **Gumroad** — package the R630 GPU→Ollama build as a paid guide (passive income)
|
||||||
|
|
||||||
|
> Fiverr is optional 4th; skip it initially — Upwork + Reddit + Gumroad is the right
|
||||||
|
> concentration. Fiverr's race-to-the-bottom pricing fights the premium positioning.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Rate research (comparable listings)
|
||||||
|
|
||||||
|
- Proxmox VE / homelab specialists on Upwork: **$40–$100/hr** (generalists $40–60, deep infra $75–100)
|
||||||
|
- GPU passthrough + local AI infra: **$75–$150/hr** (niche, high demand, low supply)
|
||||||
|
- Embedded/Android TV/ESP32: **$50–$100/hr**
|
||||||
|
- Gumroad tech guides: **$9–$49** one-time
|
||||||
|
|
||||||
|
**Recommended anchor: $75/hr** (freelance) + **$19 guide** (Gumroad). Anchor high — the
|
||||||
|
Maxwell/Vulkan and 100K-context wins are genuinely above-commodity. Don't list $20/hr;
|
||||||
|
it signals "will do anything" and attracts the worst clients.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Upwork profile (ready to paste)
|
||||||
|
|
||||||
|
**Title:**
|
||||||
|
> Self-Hosted AI & Homelab Infrastructure — GPU Passthrough, Ollama, Proxmox
|
||||||
|
|
||||||
|
**Overview:**
|
||||||
|
```
|
||||||
|
I get local AI running on hardware you own — instead of renting a cloud API.
|
||||||
|
|
||||||
|
I've built a 4-lane multi-GPU LLM architecture across an RTX 4080 SUPER, RTX 3070,
|
||||||
|
and a retired Dell R630 (Quadro M4000), with a single model standard and per-lane
|
||||||
|
latency/VRAM routing. Highlights I can reproduce for you:
|
||||||
|
|
||||||
|
• GPU passthrough + Ollama on Proxmox (incl. the CUDA→Vulkan migration when Ollama
|
||||||
|
dropped Maxwell support — a non-obvious fix most people burn hours on)
|
||||||
|
• 100K-token context on 16GB VRAM with zero CPU offload (Flash Attention + KV-cache quant)
|
||||||
|
• Multi-host Ollama routing: premium / batch / always-on lanes
|
||||||
|
• Homelab: Proxmox, Docker, ZFS (SLOG/L2ARC), backups (PBS), networking
|
||||||
|
• Hardware: Android TV rooting (Magisk/Amlogic), ESP32-S3 firmware, ADB automation
|
||||||
|
|
||||||
|
If you want a local LLM running on your GPU and it won't cooperate — I can fix it.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Skills/tags:** Proxmox VE, GPU Passthrough, Ollama, LLM, Self-Hosted, Docker, Linux
|
||||||
|
System Administration, ZFS, Homelab, NVIDIA CUDA, Vulkan.
|
||||||
|
|
||||||
|
**Rate:** $75/hr.
|
||||||
|
|
||||||
|
**Portfolio items:** attach the 3 pieces in `portfolio/` (R630 GPU/Ollama, multi-GPU
|
||||||
|
routing, SK4 Pro rooting).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Reddit presence (reputation, not a storefront)
|
||||||
|
|
||||||
|
**Handle suggestion:** something credible and technical (e.g. `selfhosted-gpu-guy` or your
|
||||||
|
existing handle). Do NOT spam links — answer help threads, drop the portfolio link only
|
||||||
|
when it's genuinely relevant.
|
||||||
|
|
||||||
|
**Primary subs:**
|
||||||
|
- r/LocalLLaMA — where "Ollama won't use my GPU" lives
|
||||||
|
- r/homelab — Proxmox/GPU passthrough audience
|
||||||
|
- r/selfhosted — self-hosted AI
|
||||||
|
- r/ollama — direct model/GPU troubleshooting
|
||||||
|
|
||||||
|
**Approach:** Help first. Each answer = proof of expertise. Link the Gumroad guide or
|
||||||
|
Upwork profile only when someone explicitly wants it done-for-them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Gumroad listing (paid guide)
|
||||||
|
|
||||||
|
**Title:** *Run Ollama on Your Own GPU: The R630 Passthrough & Multi-GPU Routing Guide*
|
||||||
|
|
||||||
|
**Pitch:**
|
||||||
|
> The exact steps to take a retired server (or any NVIDIA GPU) and turn it into a 24/7
|
||||||
|
> local LLM box — including the CUDA→Vulkan fallback fix, 100K-context-on-16GB config,
|
||||||
|
> and multi-host routing. Written from a real 3-GPU deployment, not theory.
|
||||||
|
|
||||||
|
**Price:** $19 (or $12 launch).
|
||||||
|
|
||||||
|
**Contents:**
|
||||||
|
1. Hardware + driver setup (incl. Maxwell/Vulkan gotcha)
|
||||||
|
2. Ollama as a systemd service (LAN-open, keep_alive, model pinning)
|
||||||
|
3. 100K ctx on 16GB VRAM (Flash Attention + KV-cache q4_0)
|
||||||
|
4. Multi-GPU lane routing architecture
|
||||||
|
5. Troubleshooting: `ollama ps`, nvidia-smi, cold-model timeouts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup checklist (what's DONE vs what needs YOU)
|
||||||
|
|
||||||
|
### ✅ Done (files ready in this repo)
|
||||||
|
- [x] Positioning statement + bio (`positioning.md`)
|
||||||
|
- [x] 3 portfolio pieces (`portfolio/`)
|
||||||
|
- [x] Upwork/Fiverr profile copy (above)
|
||||||
|
- [x] Gumroad listing copy (above)
|
||||||
|
- [x] Outreach targets + drafted messages (`outreach.md`)
|
||||||
|
|
||||||
|
### 🔐 Requires your identity/verification (can't be automated — and I won't fake it)
|
||||||
|
- [ ] Upwork: gov ID verification, real phone, tax info (W-9/W-8), bank payout. **Needs you.**
|
||||||
|
- [ ] Gumroad/Ko-fi: email + payout method (bank/PayPal). **Needs you.**
|
||||||
|
- [ ] Reddit: your real account (or an aged account) — reputation can't be faked. **Needs you.**
|
||||||
|
|
||||||
|
### 🚦 Sequence
|
||||||
|
1. You create/verify the 3 accounts (I can't do ID verification).
|
||||||
|
2. Paste the profile copy above (it's done).
|
||||||
|
3. Attach the 3 portfolio pieces.
|
||||||
|
4. I hand you the outreach messages; you send from your accounts (fresh accounts + mass
|
||||||
|
DMs = ban risk, so it has to be human-paced from your real account).
|
||||||
198
portfolio-site/index-v1.html
Normal file
198
portfolio-site/index-v1.html
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Indiana Holmes — Self-Hosted AI & Homelab Infrastructure</title>
|
||||||
|
<meta name="description" content="I get local AI running on hardware you own — GPU passthrough, multi-host Ollama routing, and self-hosted LLMs that never leave your network.">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#0a0e14; --bg2:#0f1520; --panel:#121a26; --line:#1e2a3a;
|
||||||
|
--text:#d7e0ea; --muted:#7f8ea3; --accent:#4ade80; --accent2:#38bdf8;
|
||||||
|
--amber:#fbbf24; --mono:'SFMono-Regular',ui-monospace,'JetBrains Mono',Consolas,monospace;
|
||||||
|
--sans:-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
|
body{background:var(--bg);color:var(--text);font-family:var(--sans);line-height:1.6;
|
||||||
|
-webkit-font-smoothing:antialiased;}
|
||||||
|
a{color:var(--accent);text-decoration:none}
|
||||||
|
a:hover{text-decoration:underline}
|
||||||
|
.wrap{max-width:860px;margin:0 auto;padding:0 24px}
|
||||||
|
.mono{font-family:var(--mono)}
|
||||||
|
.accent{color:var(--accent)}
|
||||||
|
|
||||||
|
/* nav */
|
||||||
|
nav{display:flex;justify-content:space-between;align-items:center;padding:22px 0;border-bottom:1px solid var(--line)}
|
||||||
|
.logo{font-family:var(--mono);font-weight:700;font-size:15px;letter-spacing:.5px}
|
||||||
|
.logo .prompt{color:var(--accent)}
|
||||||
|
.logo .cursor{color:var(--accent);animation:blink 1s step-end infinite}
|
||||||
|
@keyframes blink{50%{opacity:0}}
|
||||||
|
nav .links a{color:var(--muted);margin-left:22px;font-size:14px}
|
||||||
|
nav .links a:hover{color:var(--text)}
|
||||||
|
|
||||||
|
/* hero */
|
||||||
|
.hero{padding:72px 0 48px}
|
||||||
|
.hero .tag{display:inline-block;font-family:var(--mono);font-size:12px;color:var(--accent);
|
||||||
|
border:1px solid var(--line);background:var(--bg2);padding:4px 12px;border-radius:20px;margin-bottom:22px;letter-spacing:.5px}
|
||||||
|
h1{font-size:44px;line-height:1.15;font-weight:800;letter-spacing:-.5px;margin-bottom:20px}
|
||||||
|
h1 .hl{color:var(--accent)}
|
||||||
|
.lede{font-size:19px;color:var(--muted);max-width:640px;margin-bottom:28px}
|
||||||
|
.cta{display:flex;gap:14px;flex-wrap:wrap}
|
||||||
|
.btn{display:inline-block;padding:13px 24px;border-radius:8px;font-weight:600;font-size:15px;border:1px solid transparent}
|
||||||
|
.btn.primary{background:var(--accent);color:#04140a}
|
||||||
|
.btn.primary:hover{background:#5ee89a;text-decoration:none}
|
||||||
|
.btn.ghost{border-color:var(--line);color:var(--text)}
|
||||||
|
.btn.ghost:hover{border-color:var(--accent);text-decoration:none}
|
||||||
|
|
||||||
|
/* stats */
|
||||||
|
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line);
|
||||||
|
border:1px solid var(--line);border-radius:12px;overflow:hidden;margin:8px 0 64px}
|
||||||
|
.stat{background:var(--panel);padding:22px;text-align:center}
|
||||||
|
.stat .n{font-family:var(--mono);font-size:28px;font-weight:700;color:var(--accent)}
|
||||||
|
.stat .l{font-size:12.5px;color:var(--muted);margin-top:4px}
|
||||||
|
|
||||||
|
/* sections */
|
||||||
|
section{padding:40px 0}
|
||||||
|
.sec-head{font-family:var(--mono);font-size:13px;color:var(--accent2);letter-spacing:1px;
|
||||||
|
text-transform:uppercase;margin-bottom:24px}
|
||||||
|
.sec-head::before{content:"▸ ";color:var(--accent)}
|
||||||
|
|
||||||
|
h2{font-size:28px;font-weight:800;letter-spacing:-.3px;margin-bottom:14px}
|
||||||
|
.sub{color:var(--muted);font-size:16px;margin-bottom:32px;max-width:640px}
|
||||||
|
|
||||||
|
/* projects */
|
||||||
|
.project{background:var(--panel);border:1px solid var(--line);border-radius:12px;
|
||||||
|
padding:30px;margin-bottom:22px}
|
||||||
|
.project h3{font-size:21px;font-weight:700;margin-bottom:6px}
|
||||||
|
.project .meta{font-family:var(--mono);font-size:12px;color:var(--muted);margin-bottom:18px}
|
||||||
|
.project .grid{display:grid;grid-template-columns:1fr;gap:18px}
|
||||||
|
.prow{display:grid;grid-template-columns:120px 1fr;gap:16px}
|
||||||
|
.prow .k{font-family:var(--mono);font-size:13px;color:var(--amber);font-weight:600;text-transform:uppercase;letter-spacing:.5px}
|
||||||
|
.prow .v{font-size:15px;color:var(--text)}
|
||||||
|
.tools{margin-top:18px;padding-top:16px;border-top:1px solid var(--line)}
|
||||||
|
.tools span{display:inline-block;font-family:var(--mono);font-size:12px;color:var(--accent2);
|
||||||
|
background:var(--bg2);border:1px solid var(--line);border-radius:6px;padding:3px 10px;margin:0 6px 6px 0}
|
||||||
|
|
||||||
|
/* services */
|
||||||
|
.svc{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
||||||
|
.svc-item{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:24px}
|
||||||
|
.svc-item h4{font-size:16px;font-weight:700;margin-bottom:6px}
|
||||||
|
.svc-item .rate{font-family:var(--mono);color:var(--accent);font-size:15px;font-weight:600}
|
||||||
|
.svc-item p{font-size:14px;color:var(--muted);margin-top:8px}
|
||||||
|
|
||||||
|
/* contact */
|
||||||
|
.contact{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:36px;text-align:center;margin-bottom:40px}
|
||||||
|
.contact h2{margin-bottom:10px}
|
||||||
|
.contact p{color:var(--muted);margin-bottom:22px}
|
||||||
|
.contact .email{font-family:var(--mono);font-size:17px;color:var(--accent)}
|
||||||
|
|
||||||
|
footer{border-top:1px solid var(--line);padding:28px 0 40px;text-align:center;color:var(--muted);font-size:13px}
|
||||||
|
footer a{color:var(--muted)}
|
||||||
|
|
||||||
|
@media(max-width:680px){
|
||||||
|
h1{font-size:32px}
|
||||||
|
.stats{grid-template-columns:repeat(2,1fr)}
|
||||||
|
.prow{grid-template-columns:1fr}
|
||||||
|
.svc{grid-template-columns:1fr}
|
||||||
|
nav .links a{display:none}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
<nav>
|
||||||
|
<div class="logo"><span class="prompt">~</span> ih<span class="cursor">▊</span></div>
|
||||||
|
<div class="links">
|
||||||
|
<a href="#work">work</a>
|
||||||
|
<a href="#services">services</a>
|
||||||
|
<a href="#contact">contact</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<header class="hero">
|
||||||
|
<span class="tag">self-hosted AI · homelab · hardware</span>
|
||||||
|
<h1>I get local AI running on<br>hardware <span class="hl">you own</span>.</h1>
|
||||||
|
<p class="lede">GPU passthrough, multi-host Ollama routing, and self-hosted LLMs that never
|
||||||
|
leave your network. If your GPU won't cooperate with Ollama, I can fix it.</p>
|
||||||
|
<div class="cta">
|
||||||
|
<a class="btn primary" href="#contact">Hire me</a>
|
||||||
|
<a class="btn ghost" href="#work">See the work</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="stats">
|
||||||
|
<div class="stat"><div class="n">4</div><div class="l">GPU lanes routed</div></div>
|
||||||
|
<div class="stat"><div class="n">100K</div><div class="l">ctx on 16GB VRAM</div></div>
|
||||||
|
<div class="stat"><div class="n">13.2</div><div class="l">tok/s on a $100 GPU</div></div>
|
||||||
|
<div class="stat"><div class="n">3</div><div class="l">hosts, one model std</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section id="work">
|
||||||
|
<div class="sec-head">01 · Selected work</div>
|
||||||
|
<h2>Proof, not promises.</h2>
|
||||||
|
<p class="sub">Real builds from real hardware. Each one solves the exact problem people
|
||||||
|
pay to have solved.</p>
|
||||||
|
|
||||||
|
<div class="project">
|
||||||
|
<h3>R630 → Always-On Ollama Inference (GPU Passthrough)</h3>
|
||||||
|
<div class="meta">Dell R630 · Quadro M4000 · Vulkan fallback · 13.2 tok/s</div>
|
||||||
|
<div class="prow"><div class="k">Problem</div><div class="v">A retired R630 needed to become a 24/7 local LLM box for a fleet of apps — instead of each app spinning up its own Ollama or renting cloud GPU time.</div></div>
|
||||||
|
<div class="prow"><div class="k">Approach</div><div class="v">Docker + nvidia-toolkit, Ollama as a systemd service (LAN-open, keep_alive, model pinning). Diagnosed the undocumented gotcha: Ollama 0.34 dropped CUDA for Maxwell, so it silently fell back to Vulkan — benchmarked it properly instead of declaring it dead.</div></div>
|
||||||
|
<div class="prow"><div class="k">Result</div><div class="v">ornith-1.5:9b at 100% GPU, 13.2 tok/s on a $100 retired card — the always-on consolidation lane every server app now points at.</div></div>
|
||||||
|
<div class="tools"><span>Proxmox</span><span>Docker</span><span>Ollama</span><span>Vulkan</span><span>ZFS</span><span>systemd</span><span>iDRAC</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project">
|
||||||
|
<h3>4-Lane Multi-GPU LLM Routing Architecture</h3>
|
||||||
|
<div class="meta">RTX 4080 SUPER · RTX 3070 · M4000 · MacBook · one model standard</div>
|
||||||
|
<div class="prow"><div class="k">Problem</div><div class="v">Three GPUs + a MacBook were being used ad hoc — every app guessed where to send its LLM call, causing GPU saturation, cold-model timeouts, and latency-critical jobs queued behind batch work.</div></div>
|
||||||
|
<div class="prow"><div class="k">Approach</div><div class="v">Four explicit lanes (premium / batch / always-on / voice) with a single model standard and per-lane latency/VRAM routing. Shared one GPU between Ollama and ComfyUI via HyperSwap.</div></div>
|
||||||
|
<div class="prow"><div class="k">Result</div><div class="v">100K-token context on 16GB VRAM with zero CPU offload; prefill pushed 308 → 1867 tok/s. Every consumer mapped to the lane that fits its class.</div></div>
|
||||||
|
<div class="tools"><span>Ollama multi-host</span><span>CUDA</span><span>Vulkan</span><span>Flash Attention</span><span>KV-cache quant</span><span>HyperSwap</span><span>ComfyUI</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="project">
|
||||||
|
<h3>Android TV Box Rooting + Emulation (SK4 Pro)</h3>
|
||||||
|
<div class="meta">UGOOS SK4 Pro · Amlogic · Android 14 · Magisk · NetherSX2</div>
|
||||||
|
<div class="prow"><div class="k">Problem</div><div class="v">Turn an Android TV box into a retro-emulation console (N64/PS1/PS2) — which required root, BIOS/ROM staging, and core config, all below the app layer.</div></div>
|
||||||
|
<div class="prow"><div class="k">Approach</div><div class="v">Pushed Magisk 30.7, patched the A/B boot image, staged PS1/PS2 BIOS + ROMs, configured RetroArch cores, and managed it over ADB + SMB.</div></div>
|
||||||
|
<div class="prow"><div class="k">Result</div><div class="v">A rooted, emulation-ready TV box with clean ROM/BIOS layout, side-loaded Jellyfin, managed fully remote over the LAN.</div></div>
|
||||||
|
<div class="tools"><span>Amlogic A/B</span><span>Magisk</span><span>ADB</span><span>RetroArch</span><span>NetherSX2</span><span>Samba</span><span>ESP32-S3</span></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="services">
|
||||||
|
<div class="sec-head">02 · What I do</div>
|
||||||
|
<h2>Services & rates.</h2>
|
||||||
|
<div class="svc">
|
||||||
|
<div class="svc-item"><h4>Self-hosted AI / GPU passthrough</h4><div class="rate">$75/hr</div><p>Local LLM on your own hardware — GPU passthrough, Ollama setup, multi-host routing, context/VRAM optimization.</p></div>
|
||||||
|
<div class="svc-item"><h4>Homelab & sysadmin</h4><div class="rate">$65/hr</div><p>Proxmox, Docker, ZFS, backups (PBS), networking. Turn a pile of servers into one clean infrastructure.</p></div>
|
||||||
|
<div class="svc-item"><h4>Hardware / embedded</h4><div class="rate">$65/hr</div><p>Android TV rooting, ESP32 firmware, ADB automation, boot-image patching — the below-the-OS-layer stuff.</p></div>
|
||||||
|
<div class="svc-item"><h4>Defensive security review</h4><div class="rate">$95/hr</div><p>Authorized vulnerability assessment and app-security review. Scoped, documented, lawful.</p></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="contact">
|
||||||
|
<div class="sec-head">03 · Contact</div>
|
||||||
|
<div class="contact">
|
||||||
|
<h2>Have a GPU that won't cooperate?</h2>
|
||||||
|
<p>Tell me your hardware and what's stuck. I'll tell you if it's a 2-command fix or a real job.</p>
|
||||||
|
<div class="email">indianaholmes1@icloud.com</div>
|
||||||
|
<div style="margin-top:18px">
|
||||||
|
<a class="btn primary" href="mailto:indianaholmes1@icloud.com?subject=Local%20LLM%20setup">Email me</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Indiana Holmes · Self-Hosted AI & Homelab Infrastructure</p>
|
||||||
|
<p style="margin-top:8px">
|
||||||
|
<a href="https://buymeacoffee.com/r26xrthzttg" target="_blank" rel="noopener">☕ Support my work</a>
|
||||||
|
· <span class="mono">upwork · reddit · gumroad</span>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
598
portfolio-site/index.html
Normal file
598
portfolio-site/index.html
Normal file
@@ -0,0 +1,598 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Indiana Holmes — Generalist Engineer & Self-Hosted Builder</title>
|
||||||
|
<meta name="description" content="A self-taught generalist who builds and runs his own servers, AI, firmware, and payments. Everything here is live.">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--bg:#04060d; --panel:rgba(14,18,32,.52); --panel2:rgba(20,26,46,.62);
|
||||||
|
--line:rgba(120,160,255,.13); --text:#e7ecff; --muted:#8b97b6;
|
||||||
|
--cyan:#67e8f9; --violet:#a78bfa; --green:#4ade80; --amber:#fbbf24; --ember:#ff6b3d;
|
||||||
|
--mono:'SFMono-Regular',ui-monospace,'JetBrains Mono',Consolas,monospace;
|
||||||
|
--sans:-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
|
html{scroll-behavior:smooth}
|
||||||
|
body{
|
||||||
|
background:var(--bg);color:var(--text);font-family:var(--sans);line-height:1.65;
|
||||||
|
-webkit-font-smoothing:antialiased;overflow-x:hidden;
|
||||||
|
}
|
||||||
|
/* live physics background */
|
||||||
|
#cosmos{position:fixed;inset:0;z-index:-1;display:block}
|
||||||
|
|
||||||
|
a{color:var(--cyan);text-decoration:none;transition:.2s}
|
||||||
|
a:hover{text-shadow:0 0 18px rgba(103,232,249,.55)}
|
||||||
|
.wrap{max-width:1040px;margin:0 auto;padding:0 30px}
|
||||||
|
.mono{font-family:var(--mono)}
|
||||||
|
|
||||||
|
.reveal{opacity:0;transform:translateY(26px);transition:opacity .8s ease,transform .8s ease}
|
||||||
|
.reveal.in{opacity:1;transform:none}
|
||||||
|
|
||||||
|
/* nav */
|
||||||
|
nav{position:sticky;top:0;z-index:10;display:flex;justify-content:space-between;align-items:center;
|
||||||
|
padding:18px 0;backdrop-filter:blur(14px);
|
||||||
|
background:linear-gradient(180deg,rgba(4,6,13,.82),rgba(4,6,13,.35));}
|
||||||
|
.logo{font-family:var(--mono);font-weight:700;font-size:15px;letter-spacing:.5px}
|
||||||
|
.logo .prompt{color:var(--green)}
|
||||||
|
.logo .cursor{color:var(--green);animation:blink 1s step-end infinite}
|
||||||
|
@keyframes blink{50%{opacity:0}}
|
||||||
|
nav .links a{color:var(--muted);margin-left:22px;font-size:14px}
|
||||||
|
nav .links a:hover{color:var(--text);text-shadow:none}
|
||||||
|
|
||||||
|
/* hero */
|
||||||
|
.hero{padding:110px 0 40px;text-align:left;min-height:72vh;display:flex;flex-direction:column;justify-content:center}
|
||||||
|
.badge{display:inline-block;font-family:var(--mono);font-size:12px;color:var(--cyan);
|
||||||
|
border:1px solid var(--line);background:var(--panel);padding:5px 14px;border-radius:20px;
|
||||||
|
margin-bottom:26px;letter-spacing:1px;backdrop-filter:blur(8px);width:fit-content}
|
||||||
|
h1{font-size:56px;line-height:1.08;font-weight:800;letter-spacing:-.8px;margin-bottom:24px;max-width:760px}
|
||||||
|
h1 .hl{background:linear-gradient(92deg,var(--ember),var(--amber),var(--cyan));
|
||||||
|
-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||||
|
.term{font-family:var(--mono);font-size:16px;color:var(--green);min-height:1.6em;margin-bottom:22px}
|
||||||
|
.term .dim{color:var(--muted)}
|
||||||
|
.lede{font-size:19px;color:var(--muted);max-width:660px;margin-bottom:32px}
|
||||||
|
.lede b{color:var(--text);font-weight:600}
|
||||||
|
.cta{display:flex;gap:14px;flex-wrap:wrap}
|
||||||
|
.btn{display:inline-block;padding:14px 28px;border-radius:10px;font-weight:600;font-size:15px;
|
||||||
|
border:1px solid transparent;transition:.25s;cursor:pointer}
|
||||||
|
.btn.primary{background:linear-gradient(90deg,var(--cyan),var(--violet));color:#04060d}
|
||||||
|
.btn.primary:hover{box-shadow:0 0 28px rgba(103,232,249,.45);transform:translateY(-2px);text-shadow:none}
|
||||||
|
.btn.ghost{border-color:var(--line);color:var(--text);background:var(--panel);backdrop-filter:blur(8px)}
|
||||||
|
.btn.ghost:hover{border-color:var(--cyan);box-shadow:0 0 20px rgba(103,232,249,.2)}
|
||||||
|
|
||||||
|
/* stats */
|
||||||
|
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line);
|
||||||
|
border:1px solid var(--line);border-radius:14px;overflow:hidden;margin:8px 0 76px;
|
||||||
|
backdrop-filter:blur(8px)}
|
||||||
|
.stat{background:var(--panel);padding:24px;text-align:center;transition:.3s}
|
||||||
|
.stat:hover{background:var(--panel2)}
|
||||||
|
.stat .n{font-family:var(--mono);font-size:28px;font-weight:700;
|
||||||
|
background:linear-gradient(90deg,var(--ember),var(--amber));
|
||||||
|
-webkit-background-clip:text;background-clip:text;color:transparent}
|
||||||
|
.stat .l{font-size:12.5px;color:var(--muted);margin-top:5px}
|
||||||
|
|
||||||
|
/* sections */
|
||||||
|
section{padding:46px 0}
|
||||||
|
.sec-head{font-family:var(--mono);font-size:13px;color:var(--violet);letter-spacing:2px;
|
||||||
|
text-transform:uppercase;margin-bottom:20px}
|
||||||
|
.sec-head::before{content:"▸ ";color:var(--cyan)}
|
||||||
|
h2{font-size:32px;font-weight:800;letter-spacing:-.3px;margin-bottom:12px}
|
||||||
|
.sub{color:var(--muted);font-size:16px;margin-bottom:34px;max-width:680px}
|
||||||
|
|
||||||
|
/* skills */
|
||||||
|
.skillgroup{margin-bottom:26px}
|
||||||
|
.skillgroup h4{font-family:var(--mono);font-size:12px;color:var(--amber);letter-spacing:1.5px;
|
||||||
|
text-transform:uppercase;margin-bottom:12px;font-weight:600}
|
||||||
|
.skills{display:flex;flex-wrap:wrap;gap:9px}
|
||||||
|
.skill{font-family:var(--mono);font-size:13px;color:var(--cyan);background:var(--panel);
|
||||||
|
border:1px solid var(--line);border-radius:8px;padding:7px 13px;transition:.25s;cursor:default}
|
||||||
|
.skill:hover{transform:translateY(-3px);border-color:var(--cyan);box-shadow:0 6px 20px rgba(103,232,249,.2)}
|
||||||
|
|
||||||
|
/* projects */
|
||||||
|
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px}
|
||||||
|
.card{position:relative;background:var(--panel);border:1px solid var(--line);border-radius:16px;
|
||||||
|
padding:26px;backdrop-filter:blur(10px);overflow:hidden;transition:transform .3s ease,border-color .3s ease,box-shadow .3s ease;
|
||||||
|
display:flex;flex-direction:column;gap:12px}
|
||||||
|
.card::before{content:"";position:absolute;inset:0;border-radius:16px;pointer-events:none;
|
||||||
|
background:radial-gradient(500px 240px at var(--mx,50%) var(--my,0%),rgba(255,107,61,.14),transparent 60%);
|
||||||
|
opacity:0;transition:opacity .3s}
|
||||||
|
.card:hover::before{opacity:1}
|
||||||
|
.card:hover{transform:translateY(-4px);border-color:rgba(255,107,61,.45);box-shadow:0 18px 44px rgba(0,0,0,.55)}
|
||||||
|
.card h3{font-size:18px;font-weight:700;line-height:1.25}
|
||||||
|
.card h3 a{color:var(--text)}
|
||||||
|
.card h3 a:hover{color:var(--cyan);text-shadow:none}
|
||||||
|
.card .host{font-family:var(--mono);font-size:11px;color:var(--violet);letter-spacing:.2px}
|
||||||
|
.card .what{font-size:14px;color:var(--muted);flex:1}
|
||||||
|
.card .why{border-top:1px solid var(--line);padding-top:12px;font-size:13px;color:var(--amber)}
|
||||||
|
.card .why b{font-family:var(--mono);font-size:11px;color:var(--cyan);letter-spacing:1px;
|
||||||
|
text-transform:uppercase;display:block;margin-bottom:3px;font-weight:600}
|
||||||
|
|
||||||
|
/* timeline */
|
||||||
|
.tl{position:relative;margin:20px 0 8px;padding-left:28px}
|
||||||
|
.tl::before{content:"";position:absolute;left:8px;top:4px;bottom:4px;width:1px;
|
||||||
|
background:linear-gradient(180deg,var(--cyan),var(--violet),transparent)}
|
||||||
|
.tl-item{position:relative;margin-bottom:28px}
|
||||||
|
.tl-item::before{content:"";position:absolute;left:-26px;top:6px;width:11px;height:11px;border-radius:50%;
|
||||||
|
background:var(--bg);border:2px solid var(--cyan);box-shadow:0 0 12px rgba(103,232,249,.6)}
|
||||||
|
.tl-item .when{font-family:var(--mono);font-size:12px;color:var(--violet);letter-spacing:.5px}
|
||||||
|
.tl-item .what{font-weight:700;font-size:16px;margin:2px 0}
|
||||||
|
.tl-item .where{font-size:14px;color:var(--muted)}
|
||||||
|
|
||||||
|
/* services */
|
||||||
|
.svc{display:grid;grid-template-columns:1fr 1fr;gap:16px}
|
||||||
|
.svc-item{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:26px;
|
||||||
|
backdrop-filter:blur(8px);transition:.3s}
|
||||||
|
.svc-item:hover{transform:translateY(-4px);border-color:var(--violet);box-shadow:0 12px 30px rgba(167,139,250,.15)}
|
||||||
|
.svc-item h4{font-size:16px;font-weight:700;margin-bottom:4px}
|
||||||
|
.svc-item .rate{font-family:var(--mono);color:var(--green);font-size:16px;font-weight:600}
|
||||||
|
.svc-item p{font-size:13.5px;color:var(--muted);margin-top:8px}
|
||||||
|
|
||||||
|
/* contact */
|
||||||
|
.contact{background:var(--panel);border:1px solid var(--line);border-radius:18px;padding:46px;
|
||||||
|
text-align:center;margin:40px 0 48px;backdrop-filter:blur(10px);position:relative;overflow:hidden}
|
||||||
|
.contact::before{content:"";position:absolute;inset:0;pointer-events:none;
|
||||||
|
background:radial-gradient(500px 200px at 50% 0%,rgba(255,107,61,.12),transparent 70%)}
|
||||||
|
.contact h2{margin-bottom:10px}
|
||||||
|
.contact p{color:var(--muted);margin-bottom:24px;position:relative}
|
||||||
|
.contact .email{font-family:var(--mono);font-size:18px;color:var(--cyan);position:relative}
|
||||||
|
.contact .cta{justify-content:center;position:relative;margin-top:20px}
|
||||||
|
|
||||||
|
footer{border-top:1px solid var(--line);padding:30px 0 46px;text-align:center;color:var(--muted);font-size:13px}
|
||||||
|
footer a{color:var(--muted)}
|
||||||
|
|
||||||
|
@media(max-width:860px){
|
||||||
|
.grid{grid-template-columns:repeat(2,1fr)}
|
||||||
|
}
|
||||||
|
@media(max-width:680px){
|
||||||
|
h1{font-size:36px}
|
||||||
|
.stats{grid-template-columns:repeat(2,1fr)}
|
||||||
|
.grid{grid-template-columns:1fr}
|
||||||
|
.svc{grid-template-columns:1fr}
|
||||||
|
nav .links a{display:none}
|
||||||
|
.hero{min-height:auto;padding:80px 0 32px}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<canvas id="cosmos"></canvas>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
<nav>
|
||||||
|
<div class="logo"><span class="prompt">~</span> indiana.holmes<span class="cursor">▊</span></div>
|
||||||
|
<div class="links">
|
||||||
|
<a href="#skills">skills</a>
|
||||||
|
<a href="#work">built</a>
|
||||||
|
<a href="#services">services</a>
|
||||||
|
<a href="#contact">contact</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<header class="hero">
|
||||||
|
<span class="badge reveal">generalist engineer · self-taught · runs his own lab</span>
|
||||||
|
<h1 class="reveal">I build things on computers — and run most of them <span class="hl">myself</span>.</h1>
|
||||||
|
<div class="term reveal"><span class="dim">$</span> <span id="typewriter"></span><span class="cursor">▊</span></div>
|
||||||
|
<p class="lede reveal">Firmware, servers, AI, payments, and the web — I've taught myself the whole stack by building it.
|
||||||
|
Everything below is <b>live and real</b>, not mockups: the actual services running in my lab right now.</p>
|
||||||
|
<div class="cta reveal">
|
||||||
|
<a class="btn primary" href="#contact">Get in touch</a>
|
||||||
|
<a class="btn ghost" href="#work">See what I run ↓</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="stats reveal">
|
||||||
|
<div class="stat"><div class="n">69</div><div class="l">services running</div></div>
|
||||||
|
<div class="stat"><div class="n">3,685</div><div class="l">MCP servers indexed</div></div>
|
||||||
|
<div class="stat"><div class="n">31</div><div class="l">Bitcoin stores</div></div>
|
||||||
|
<div class="stat"><div class="n">8</div><div class="l">sites self-publishing</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section id="skills" class="reveal">
|
||||||
|
<div class="sec-head">01 · Capabilities</div>
|
||||||
|
<h2>A broad toolbox.</h2>
|
||||||
|
<p class="sub">I'm a generalist — I move between the OS layer, the network, the models, and the metal. Here's what I actually reach for.</p>
|
||||||
|
|
||||||
|
<div class="skillgroup"><h4>Systems & infrastructure</h4>
|
||||||
|
<div class="skills">
|
||||||
|
<span class="skill">Proxmox</span><span class="skill">Docker</span><span class="skill">ZFS · SLOG/L2ARC</span>
|
||||||
|
<span class="skill">Linux · systemd</span><span class="skill">nginx</span><span class="skill">Networking</span>
|
||||||
|
<span class="skill">Cloudflare Tunnels</span><span class="skill">Backups · PBS</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="skillgroup"><h4>AI & models</h4>
|
||||||
|
<div class="skills">
|
||||||
|
<span class="skill">Ollama · multi-host</span><span class="skill">GPU passthrough</span>
|
||||||
|
<span class="skill">Local LLMs</span><span class="skill">Flash Attention</span><span class="skill">KV-cache quant</span>
|
||||||
|
<span class="skill">MCP / agents</span><span class="skill">ComfyUI</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="skillgroup"><h4>Software</h4>
|
||||||
|
<div class="skills">
|
||||||
|
<span class="skill">Python</span><span class="skill">JavaScript</span><span class="skill">Flask</span>
|
||||||
|
<span class="skill">React / Vite</span><span class="skill">SQL</span><span class="skill">REST APIs</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="skillgroup"><h4>Hardware & embedded</h4>
|
||||||
|
<div class="skills">
|
||||||
|
<span class="skill">ESP32 · firmware</span><span class="skill">ADB / Android</span><span class="skill">Magisk</span>
|
||||||
|
<span class="skill">Amlogic A/B</span><span class="skill">Boot images</span><span class="skill">RetroArch</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="skillgroup"><h4>Payments & security</h4>
|
||||||
|
<div class="skills">
|
||||||
|
<span class="skill">Bitcoin · Lightning</span><span class="skill">BTCPay</span><span class="skill">Self-hosted payments</span>
|
||||||
|
<span class="skill">Defensive security</span><span class="skill">OSINT</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="work" class="reveal">
|
||||||
|
<div class="sec-head">02 · Built & running</div>
|
||||||
|
<h2>Things I've built — and still run.</h2>
|
||||||
|
<p class="sub">Each one is linked and live. I didn't build these for a resume; I built them because I wanted them, and they're up 24/7.</p>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
|
||||||
|
<div class="card" data-tilt>
|
||||||
|
<h3><a href="https://mcp.thetempleofdoom.com" target="_blank" rel="noopener">Omninexus</a></h3>
|
||||||
|
<div class="host">mcp.thetempleofdoom.com</div>
|
||||||
|
<div class="what">An MCP hub that indexes 3,685 AI servers — discovery, search, and health checks in one place.</div>
|
||||||
|
<div class="why"><b>why it matters</b> Agents discover any capability instead of hard-coding integrations. One endpoint for thousands of tools.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-tilt>
|
||||||
|
<h3><a href="https://hyperion.thetempleofdoom.com" target="_blank" rel="noopener">Hyperion</a></h3>
|
||||||
|
<div class="host">hyperion.thetempleofdoom.com</div>
|
||||||
|
<div class="what">An app store for AI agents — publish, discover, and call MCP tools, paid over Bitcoin.</div>
|
||||||
|
<div class="why"><b>why it matters</b> Machine-to-machine billing: an agent pays for a tool the same way you'd buy an API key.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-tilt>
|
||||||
|
<h3><a href="https://argus.thetempleofdoom.com" target="_blank" rel="noopener">ARGUS</a></h3>
|
||||||
|
<div class="host">argus.thetempleofdoom.com</div>
|
||||||
|
<div class="what">Rent-a-red-teamer — web-vuln scanning and OSINT for autonomous agents, running on Kali.</div>
|
||||||
|
<div class="why"><b>why it matters</b> Turns a red-team engagement into a metered API call instead of a manual project.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-tilt>
|
||||||
|
<h3><a href="https://astraea.thetempleofdoom.com" target="_blank" rel="noopener">Astraea</a></h3>
|
||||||
|
<div class="host">astraea.thetempleofdoom.com</div>
|
||||||
|
<div class="what">A Washington divorce-law assistant with RAG over verified statute and DV-safety guardrails.</div>
|
||||||
|
<div class="why"><b>why it matters</b> Answers grounded in real law — not a model hallucinating case law from memory.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-tilt>
|
||||||
|
<h3><a href="https://thetempleofdoom.com" target="_blank" rel="noopener">The Temple</a></h3>
|
||||||
|
<div class="host">thetempleofdoom.com</div>
|
||||||
|
<div class="what">A live index of all 69 self-hosted services, plus a provably-fair Bitcoin raffle.</div>
|
||||||
|
<div class="why"><b>why it matters</b> One honest dashboard for an entire fleet — every card is a live service, not a screenshot.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-tilt>
|
||||||
|
<h3><a href="https://tarot.thetempleofdoom.com" target="_blank" rel="noopener">Tarro</a></h3>
|
||||||
|
<div class="host">tarot.thetempleofdoom.com</div>
|
||||||
|
<div class="what">A tarot reader that quotes Waite and Wilhelm/Baynes instead of guessing.</div>
|
||||||
|
<div class="why"><b>why it matters</b> Knowledge apps that cite their sources — the model reads real texts, not vibes.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-tilt>
|
||||||
|
<h3><a href="https://nebula.thetempleofdoom.com" target="_blank" rel="noopener">Nebula</a></h3>
|
||||||
|
<div class="host">nebula.thetempleofdoom.com</div>
|
||||||
|
<div class="what">A paid image-generation GUI on a self-hosted ComfyUI stack.</div>
|
||||||
|
<div class="why"><b>why it matters</b> Consumer-grade UI on raw diffusion models — no cloud, your own GPU.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-tilt>
|
||||||
|
<h3><a href="https://ai.thetempleofdoom.com" target="_blank" rel="noopener">Publisher fleet</a></h3>
|
||||||
|
<div class="host">ai · tech · science · crypto · linux · gaming · diy · guides</div>
|
||||||
|
<div class="what">Eight authority sites that research, write, and self-publish — four posts a day each.</div>
|
||||||
|
<div class="why"><b>why it matters</b> A content engine that runs itself, end to end, with no one at the wheel.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" data-tilt>
|
||||||
|
<h3>Multi-GPU LLM routing</h3>
|
||||||
|
<div class="host">3 GPUs + a MacBook · internal</div>
|
||||||
|
<div class="what">Four GPU lanes and one model standard across three GPUs and a MacBook, routed by latency and VRAM.</div>
|
||||||
|
<div class="why"><b>why it matters</b> Any app gets the right model on the right lane — no saturation, no cold-model timeouts.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="sub" style="margin-top:30px">And more: <a href="https://evergreenapplicator.thetempleofdoom.com" target="_blank" rel="noopener">Evergreen Applicator</a> (WA pesticide exam prep),
|
||||||
|
<a href="https://signalsearcher.thetempleofdoom.com" target="_blank" rel="noopener">Signal Miner</a> (Telegram market intel),
|
||||||
|
<a href="https://rigel.thetempleofdoom.com" target="_blank" rel="noopener">Rigel</a> (no-KYC SOCKS5),
|
||||||
|
<a href="https://lynx.thetempleofdoom.com" target="_blank" rel="noopener">LYNX</a> (forensics API),
|
||||||
|
<a href="https://supernova.thetempleofdoom.com" target="_blank" rel="noopener">Supernova</a> (SMM) — the full list is at
|
||||||
|
<a href="https://thetempleofdoom.com" target="_blank" rel="noopener">thetempleofdoom.com</a>.</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="experience" class="reveal">
|
||||||
|
<div class="sec-head">03 · Trajectory</div>
|
||||||
|
<h2>How I got here.</h2>
|
||||||
|
<p class="sub">Self-taught. I got into this by running my own servers and breaking them a lot.</p>
|
||||||
|
<div class="tl">
|
||||||
|
<div class="tl-item"><div class="when">2024 — now</div><div class="what">Independent engineer</div><div class="where">Built and run a homelab: a Proxmox fleet, GPU LLM routing, and a stack of self-hosted services and apps.</div></div>
|
||||||
|
<div class="tl-item"><div class="when">2023 — now</div><div class="what">Field operations & logistics</div><div class="where">Lime — operations in the field; honed the "diagnose and fix under pressure" mindset.</div></div>
|
||||||
|
<div class="tl-item"><div class="when">2014 — 2023</div><div class="what">Master grower / controlled-environment agriculture</div><div class="where">Managed precision environments — the same systems thinking I apply to hardware.</div></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="services" class="reveal">
|
||||||
|
<div class="sec-head">04 · Services & rates</div>
|
||||||
|
<h2>What I can help with.</h2>
|
||||||
|
<div class="svc">
|
||||||
|
<div class="svc-item"><h4>Self-hosted AI / GPU passthrough</h4><div class="rate">$75/hr</div><p>Local LLM on your own hardware — GPU passthrough, Ollama setup, multi-host routing, context/VRAM optimization.</p></div>
|
||||||
|
<div class="svc-item"><h4>Homelab & sysadmin</h4><div class="rate">$65/hr</div><p>Proxmox, Docker, ZFS, backups, networking. Turn a pile of servers into one clean infrastructure.</p></div>
|
||||||
|
<div class="svc-item"><h4>Hardware / embedded</h4><div class="rate">$65/hr</div><p>Android TV rooting, ESP32 firmware, ADB automation, boot-image patching — the below-the-OS-layer stuff.</p></div>
|
||||||
|
<div class="svc-item"><h4>Defensive security review</h4><div class="rate">$95/hr</div><p>Authorized vulnerability assessment and app-security review. Scoped, documented, lawful.</p></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="contact" class="reveal">
|
||||||
|
<div class="sec-head">05 · Contact</div>
|
||||||
|
<div class="contact">
|
||||||
|
<h2>Building something similar? Stuck on a local LLM or GPU setup?</h2>
|
||||||
|
<p>Tell me what you're running and what's failing. Sometimes it's a two-command fix.</p>
|
||||||
|
<div class="email">indianaholmes1@icloud.com</div>
|
||||||
|
<div class="cta">
|
||||||
|
<a class="btn primary" href="mailto:indianaholmes1@icloud.com?subject=Project">Email me</a>
|
||||||
|
<a class="btn ghost" href="https://thetempleofdoom.com" target="_blank" rel="noopener">See the whole lab</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Indiana Holmes · Generalist Engineer · Self-Hosted Builder</p>
|
||||||
|
<p style="margin-top:8px">
|
||||||
|
<a href="https://buymeacoffee.com/r26xrthzttg" target="_blank" rel="noopener">☕ Support my work</a>
|
||||||
|
· <span class="mono">upwork · reddit · gumroad</span>
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/* ═══════════════════════════════════════════════════════════
|
||||||
|
BACKGROUND — a physics-bound black hole with a fire accretion
|
||||||
|
disk. Orbits are Keplerian (inner faster), particles spiral
|
||||||
|
inward, one side is doppler-bright, and both the cursor and
|
||||||
|
page scroll bend and move it. Self-contained, no libraries.
|
||||||
|
═══════════════════════════════════════════════════════════ */
|
||||||
|
const cv = document.getElementById('cosmos');
|
||||||
|
const ctx = cv.getContext('2d');
|
||||||
|
let W=0,H=0,DPR=1;
|
||||||
|
let stars=[], parts=[], jets=[], fog=[];
|
||||||
|
let bh = {x:0,y:0,R:0, px:0,py:0, tiltX:0, tiltY:0};
|
||||||
|
let scroll=0, mx=0.5, my=0.5, tmx=0.5, tmy=0.5;
|
||||||
|
|
||||||
|
function resize(){
|
||||||
|
DPR = Math.min(window.devicePixelRatio||1, 2);
|
||||||
|
W = cv.width = Math.floor(innerWidth * DPR);
|
||||||
|
H = cv.height = Math.floor(innerHeight * DPR);
|
||||||
|
cv.style.width = innerWidth+'px';
|
||||||
|
cv.style.height = innerHeight+'px';
|
||||||
|
makeStars();
|
||||||
|
makeFog();
|
||||||
|
initBH();
|
||||||
|
}
|
||||||
|
function makeStars(){
|
||||||
|
stars=[];
|
||||||
|
const n=Math.min(560, Math.max(200, Math.floor(innerWidth*innerHeight/1500)));
|
||||||
|
for(let i=0;i<n;i++){
|
||||||
|
const tint = Math.random();
|
||||||
|
const c = tint<0.72 ? '210,220,255' : (tint<0.88 ? '255,214,180' : '165,200,255');
|
||||||
|
stars.push({x:Math.random()*W, y:Math.random()*H,
|
||||||
|
r:(Math.random()*1.3+.3)*DPR, a:Math.random()*.6+.15, tw:Math.random()*6.28,
|
||||||
|
sp:Math.random()*.3+.05, c});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function makeFog(){
|
||||||
|
fog=[];
|
||||||
|
const n=7;
|
||||||
|
for(let i=0;i<n;i++){
|
||||||
|
fog.push({
|
||||||
|
x:Math.random()*W, y:Math.random()*H,
|
||||||
|
r:(Math.random()*0.35+0.25)*Math.max(W,H),
|
||||||
|
hue:Math.random()<0.5 ? 'rgba(120,140,220,' : (Math.random()<0.5 ? 'rgba(160,110,220,' : 'rgba(200,90,60,'),
|
||||||
|
a:0.02+Math.random()*0.025,
|
||||||
|
dx:(Math.random()-.5)*0.06, dy:(Math.random()-.5)*0.04
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function initBH(){
|
||||||
|
bh.R = Math.min(W,H)*0.155;
|
||||||
|
bh.x = W*0.5; bh.y = H*0.40;
|
||||||
|
bh.px = bh.x; bh.py = bh.y;
|
||||||
|
const N = Math.floor(Math.min(2200, Math.max(1100, innerWidth*innerHeight/280)));
|
||||||
|
parts=[];
|
||||||
|
for(let i=0;i<N;i++){
|
||||||
|
parts.push({
|
||||||
|
a: Math.random()*6.2832,
|
||||||
|
r: bh.R*(1.15 + Math.pow(Math.random(),1.6)*1.3),
|
||||||
|
k: 0.75 + Math.random()*0.9, // Keplerian speed factor
|
||||||
|
s: (0.5 + Math.random()*1.8)*DPR,
|
||||||
|
o: 0.35 + Math.random()*0.65, // base opacity
|
||||||
|
fl: Math.random()*6.28, // flicker phase
|
||||||
|
fs: 0.5 + Math.random()*1.5 // flicker speed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
jets=[];
|
||||||
|
for(let i=0;i<90;i++) jets.push({d:(Math.random()<.5?-1:1), t:Math.random()*6.28,
|
||||||
|
sp:0.5+Math.random()*1.2, len:(0.8+Math.random()*1.6), off:Math.random()*6.28});
|
||||||
|
}
|
||||||
|
|
||||||
|
// fire palette: deep red → orange → warm gold (never white-hot)
|
||||||
|
function fireColor(heat, o){
|
||||||
|
heat = Math.max(0, Math.min(1, heat));
|
||||||
|
let r,g,b;
|
||||||
|
if(heat < 0.5){ const t=heat/0.5; r=255; g=Math.floor(28+t*145); b=Math.floor(8+t*38); }
|
||||||
|
else { const t=(heat-0.5)/0.5; r=255; g=Math.floor(173+t*42); b=Math.floor(46+t*26); }
|
||||||
|
return `rgba(${r},${g},${b},${o})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw(t){
|
||||||
|
const rot = 0.2 + scroll*0.00035; // scroll speeds the spin (slower)
|
||||||
|
// smooth cursor + parallax
|
||||||
|
tmx += (mx-tmx)*0.05; tmy += (my-tmy)*0.05;
|
||||||
|
bh.px += ((bh.x + (tmx-.5)*46*DPR) - bh.px)*0.05;
|
||||||
|
bh.py += ((bh.y + (tmy-.5)*34*DPR + scroll*0.12) - bh.py)*0.05;
|
||||||
|
const cx=bh.px, cy=bh.py, R=bh.R*(1+scroll*0.00004);
|
||||||
|
|
||||||
|
ctx.clearRect(0,0,W,H);
|
||||||
|
|
||||||
|
// fog / nebula haze (drifts slowly)
|
||||||
|
for(const f of fog){
|
||||||
|
f.x += f.dx; f.y += f.dy;
|
||||||
|
if(f.x < -f.r) f.x = W+f.r; if(f.x > W+f.r) f.x = -f.r;
|
||||||
|
if(f.y < -f.r) f.y = H+f.r; if(f.y > H+f.r) f.y = -f.r;
|
||||||
|
const g = ctx.createRadialGradient(f.x,f.y,0, f.x,f.y,f.r);
|
||||||
|
g.addColorStop(0, f.hue + f.a + ')');
|
||||||
|
g.addColorStop(1, f.hue + '0)');
|
||||||
|
ctx.beginPath(); ctx.arc(f.x,f.y,f.r,0,7);
|
||||||
|
ctx.fillStyle=g; ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
// stars (slow drift + colored twinkle)
|
||||||
|
for(const s of stars){
|
||||||
|
s.tw+=0.012; let sy=s.y + s.sp*0.14;
|
||||||
|
if(sy>H) sy=0;
|
||||||
|
const a=s.a*(0.5+0.5*Math.sin(s.tw));
|
||||||
|
ctx.beginPath(); ctx.arc(s.x + (tmx-.5)*30*DPR*s.a, sy + (tmy-.5)*22*DPR*s.a, s.r, 0, 7);
|
||||||
|
ctx.fillStyle=`rgba(${s.c},${a})`; ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
// accretion disk (additive) — fire, dimmer
|
||||||
|
ctx.globalCompositeOperation='lighter';
|
||||||
|
const outerR = R*2.7;
|
||||||
|
// soft radiant base glow
|
||||||
|
const bgg = ctx.createRadialGradient(cx,cy,R*0.3, cx,cy,outerR);
|
||||||
|
bgg.addColorStop(0,'rgba(255,140,50,0.14)');
|
||||||
|
bgg.addColorStop(0.5,'rgba(255,85,30,0.07)');
|
||||||
|
bgg.addColorStop(1,'rgba(255,50,20,0)');
|
||||||
|
ctx.beginPath(); ctx.arc(cx,cy,outerR,0,7);
|
||||||
|
ctx.fillStyle=bgg; ctx.fill();
|
||||||
|
// continuous fire disk band (donut) — solid fire presence
|
||||||
|
const dg = ctx.createRadialGradient(cx,cy,R*0.95, cx,cy,outerR);
|
||||||
|
dg.addColorStop(0,'rgba(255,170,60,0.0)');
|
||||||
|
dg.addColorStop(0.12,'rgba(255,180,70,0.22)');
|
||||||
|
dg.addColorStop(0.45,'rgba(255,120,40,0.13)');
|
||||||
|
dg.addColorStop(1,'rgba(255,70,25,0.0)');
|
||||||
|
ctx.beginPath(); ctx.arc(cx,cy,outerR,0,7);
|
||||||
|
ctx.fillStyle=dg; ctx.fill();
|
||||||
|
for(const p of parts){
|
||||||
|
// Keplerian: angular velocity ∝ r^-1.5
|
||||||
|
const w = p.k * Math.pow(p.r/R, -1.5) * rot * 0.03;
|
||||||
|
p.a += w;
|
||||||
|
p.r *= (1 - 0.00005); // slower accretion
|
||||||
|
if(p.r < R*1.05) p.r = R*(1.15 + Math.random()*1.3); // respawn
|
||||||
|
let x = cx + Math.cos(p.a)*p.r;
|
||||||
|
let y = cy + Math.sin(p.a)*p.r;
|
||||||
|
// cursor gravity: bend nearby particles toward the cursor
|
||||||
|
const cdx = mx*W - x, cdy = my*H - y;
|
||||||
|
const cd2 = cdx*cdx + cdy*cdy;
|
||||||
|
const cr = R*2.3;
|
||||||
|
if(cd2 < cr*cr){
|
||||||
|
const f = (1 - Math.sqrt(cd2)/cr) * 0.3;
|
||||||
|
x += cdx*f; y += cdy*f;
|
||||||
|
}
|
||||||
|
// heat = closer to horizon = hotter
|
||||||
|
const heat = 1 - Math.max(0, Math.min(1, (p.r/R - 1.1)/1.35));
|
||||||
|
// doppler beaming: approaching side (velocity toward viewer) brighter
|
||||||
|
const beam = 0.55 + 0.45*Math.max(0, Math.cos(p.a + 1.2));
|
||||||
|
// flicker for flame texture
|
||||||
|
const fl = 0.85 + 0.3*Math.sin(p.fl + t*0.002*p.fs);
|
||||||
|
const alpha = p.o * (0.35 + 0.65*heat) * beam * fl;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(x, y, p.s*(0.6+heat*0.9)*fl, 0, 7);
|
||||||
|
ctx.fillStyle = fireColor(heat, alpha*0.95);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
// photon ring (softened)
|
||||||
|
const gr = ctx.createRadialGradient(cx,cy,R*0.98, cx,cy,R*1.16);
|
||||||
|
gr.addColorStop(0,'rgba(255,210,160,0)');
|
||||||
|
gr.addColorStop(0.5,'rgba(255,190,130,0.68)');
|
||||||
|
gr.addColorStop(1,'rgba(255,110,50,0)');
|
||||||
|
ctx.beginPath(); ctx.arc(cx,cy,R*1.07,0,7);
|
||||||
|
ctx.fillStyle=gr; ctx.fill();
|
||||||
|
|
||||||
|
// polar jets (fire tendrils, softened + slower)
|
||||||
|
for(const j of jets){
|
||||||
|
j.t += 0.015*j.sp;
|
||||||
|
j.off += 0.012;
|
||||||
|
const ang = j.t;
|
||||||
|
const spread = 0.06 + Math.sin(j.off)*0.03;
|
||||||
|
const len = j.len * R * (1.4 + 0.4*Math.sin(j.off));
|
||||||
|
const bx = cx + Math.sin(ang)*spread*R;
|
||||||
|
const by = cy + j.d*len;
|
||||||
|
const g = ctx.createLinearGradient(cx,cy,bx,by);
|
||||||
|
g.addColorStop(0,'rgba(255,180,110,0.28)');
|
||||||
|
g.addColorStop(1,'rgba(255,90,30,0)');
|
||||||
|
ctx.strokeStyle=g; ctx.lineWidth=DPR*1.2;
|
||||||
|
ctx.beginPath(); ctx.moveTo(cx,cy); ctx.lineTo(bx,by); ctx.stroke();
|
||||||
|
}
|
||||||
|
ctx.globalCompositeOperation='source-over';
|
||||||
|
|
||||||
|
// event horizon (dark disc, sits over the far side of the disk)
|
||||||
|
const hg = ctx.createRadialGradient(cx,cy,0, cx,cy,R);
|
||||||
|
hg.addColorStop(0,'rgba(0,0,0,1)');
|
||||||
|
hg.addColorStop(0.82,'rgba(0,0,0,1)');
|
||||||
|
hg.addColorStop(1,'rgba(0,0,0,0)');
|
||||||
|
ctx.beginPath(); ctx.arc(cx,cy,R,0,7);
|
||||||
|
ctx.fillStyle=hg; ctx.fill();
|
||||||
|
|
||||||
|
// faint lens glow toward cursor
|
||||||
|
const lx=mx*W, ly=my*H;
|
||||||
|
if(Math.hypot(lx-cx,ly-cy) < R*3.4){
|
||||||
|
const lg=ctx.createRadialGradient(lx,ly,0,lx,ly,R*0.7);
|
||||||
|
lg.addColorStop(0,'rgba(255,170,100,0.07)');
|
||||||
|
lg.addColorStop(1,'rgba(255,170,100,0)');
|
||||||
|
ctx.beginPath(); ctx.arc(lx,ly,R*0.7,0,7);
|
||||||
|
ctx.fillStyle=lg; ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener('mousemove',e=>{ mx=e.clientX/innerWidth; my=e.clientY/innerHeight; });
|
||||||
|
addEventListener('scroll',()=>{ scroll=window.scrollY||document.documentElement.scrollTop; },{passive:true});
|
||||||
|
addEventListener('resize',resize);
|
||||||
|
resize();
|
||||||
|
requestAnimationFrame(draw);
|
||||||
|
|
||||||
|
/* ── typewriter ── */
|
||||||
|
const lines=[
|
||||||
|
'$ whoami — self-taught generalist',
|
||||||
|
'$ uptime — homelab 24/7',
|
||||||
|
'$ ls /services — 69 live',
|
||||||
|
'$ ssh nightmare — 4080 ready',
|
||||||
|
];
|
||||||
|
let li=0,ci=0,del=false;
|
||||||
|
(function type(){
|
||||||
|
const el=document.getElementById('typewriter');
|
||||||
|
const line=lines[li];
|
||||||
|
if(!del){ci++;if(ci>line.length){del=true;setTimeout(type,1800);return}}
|
||||||
|
else{ci--;if(ci<0){del=false;ci=0;li=(li+1)%lines.length}}
|
||||||
|
el.textContent=line.slice(0,ci);
|
||||||
|
setTimeout(type,del?22:50);
|
||||||
|
})();
|
||||||
|
|
||||||
|
/* ── reveal on scroll ── */
|
||||||
|
const io=new IntersectionObserver(es=>es.forEach(e=>{if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target)}}),{threshold:.12});
|
||||||
|
document.querySelectorAll('.reveal').forEach(el=>io.observe(el));
|
||||||
|
|
||||||
|
/* ── card glow follows cursor (gentle) ── */
|
||||||
|
document.querySelectorAll('[data-tilt]').forEach(card=>{
|
||||||
|
card.addEventListener('mousemove',e=>{
|
||||||
|
const r=card.getBoundingClientRect();
|
||||||
|
const mxr=(e.clientX-r.left)/r.width, myr=(e.clientY-r.top)/r.height;
|
||||||
|
card.style.setProperty('--mx',(mxr*100)+'%'); card.style.setProperty('--my',(myr*100)+'%');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
portfolio-site/nginx-portfolio.conf
Normal file
7
portfolio-site/nginx-portfolio.conf
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
server {
|
||||||
|
listen 5097;
|
||||||
|
server_name _;
|
||||||
|
root /var/www/portfolio;
|
||||||
|
index index.html;
|
||||||
|
add_header Cache-Control "no-store" always;
|
||||||
|
}
|
||||||
35
portfolio/01-r630-gpu-ollama.md
Normal file
35
portfolio/01-r630-gpu-ollama.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Portfolio — R630 GPU Passthrough → Always-On Ollama Inference
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
A retired Dell R630 PowerEdge (service tag BXK1MR2) needed to become a 24/7 local
|
||||||
|
LLM inference box for a fleet of self-hosted apps — instead of each app spinning up
|
||||||
|
its own Ollama instance or renting cloud GPU time.
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
- **Hardware**: R630 with a Quadro M4000 (8GB). Expanded RAM from 64GB → 117GB, and
|
||||||
|
carved out NVMe into a 32GB SLOG + 174GB L2ARC + 32.5GB swap for ZFS.
|
||||||
|
- **GPU passthrough**: Installed Docker + `nvidia-toolkit`, attached the M4000 with
|
||||||
|
`--gpus all`, and stood up Ollama as a systemd service (`0.0.0.0:11434`, LAN-open,
|
||||||
|
`keep_alive 30m`, `max_loaded_models 1`).
|
||||||
|
- **The gotcha nobody documents**: Ollama 0.34 *dropped CUDA for Maxwell* (compute 5.2
|
||||||
|
needs driver 570+, box has 550). CUDA silently fails → Ollama falls back to **Vulkan**.
|
||||||
|
Diagnosed it, kept Vulkan, and benchmarked it properly instead of assuming it was dead.
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
- `ornith-1.5:9b` (9B, Q4_K_M, 6.6GB) runs **100% GPU at 13.2 tok/s** with a 4K context —
|
||||||
|
comparable to an RTX 3070 at 64K context, on a $100 retired GPU.
|
||||||
|
- The box is now the **always-on consolidation lane**: every server app (Tarro, PHOTON,
|
||||||
|
Signal Miner, Research Engine, any Flask/Node app with a default LLM) points at one
|
||||||
|
Ollama instead of each running its own.
|
||||||
|
|
||||||
|
## Tools used
|
||||||
|
|
||||||
|
Proxmox, Docker, nvidia-toolkit, Ollama, Vulkan, ZFS (SLOG/L2ARC), systemd, Dell iDRAC8.
|
||||||
|
|
||||||
|
## Why it's sellable
|
||||||
|
|
||||||
|
This is the exact problem people pay to solve: "I have a GPU, why won't Ollama use it?"
|
||||||
|
The Maxwell/CUDA→Vulkan migration is a real, non-obvious fix most people burn hours on.
|
||||||
40
portfolio/02-multihost-gpu-routing.md
Normal file
40
portfolio/02-multihost-gpu-routing.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Portfolio — 4-Lane Multi-GPU LLM Routing Architecture
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Three GPU machines (an RTX 4080 SUPER, an RTX 3070, and a Quadro M4000) plus a MacBook
|
||||||
|
were being used ad hoc, with every app guessing where to send its LLM calls. Result:
|
||||||
|
GPU saturation, cold-model timeouts, and latency-critical requests queued behind batch work.
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
Designed a **4-lane routing architecture** with a single model standard (`ornith-1.5:9b`)
|
||||||
|
and an explicit per-lane map:
|
||||||
|
|
||||||
|
| Lane | Host | GPU | Job |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **premium** | RTX 4080 SUPER (16GB) | CUDA | mission-critical speed only |
|
||||||
|
| **batch** | RTX 3070 (8GB, WiFi) | CUDA | stateful, long-context, vision/OCR/embeddings |
|
||||||
|
| **permanent** | Quadro M4000 (8GB) | Vulkan | always-on consolidation for server apps |
|
||||||
|
| **voice** | MacBook (unified) | Metal/MLX | local voice (gemma3:4b) |
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
- **100K-token context on 16GB VRAM with zero CPU offload** — baked `num_ctx 102400`,
|
||||||
|
`num_gpu 99`, `KV_CACHE_TYPE q4_0`, and `NUM_BATCH 2048` to push prefill from 308 →
|
||||||
|
**1867 tok/s** and decode to ~41 tok/s on the 4080.
|
||||||
|
- Single shared GPU for Ollama + ComfyUI (image gen) via HyperSwap (program swapper),
|
||||||
|
so text inference and diffusion don't fight over VRAM.
|
||||||
|
- Every consumer (TITAN, Astraea, WorkBrain, PHOTON, Honcho, 90+ cron jobs) mapped to the
|
||||||
|
lane that fits its latency/VRAM class.
|
||||||
|
|
||||||
|
## Tools used
|
||||||
|
|
||||||
|
Ollama (multi-host), CUDA + Vulkan, Flash Attention, KV-cache quantization, HyperSwap,
|
||||||
|
ComfyUI, systemd, LAN routing.
|
||||||
|
|
||||||
|
## Why it's sellable
|
||||||
|
|
||||||
|
"Run multiple LLMs across multiple GPUs without paying a cloud provider" is a real,
|
||||||
|
growing ask. The 100K-context-on-16GB result is a concrete, quantifiable win most
|
||||||
|
consultants can't show.
|
||||||
33
portfolio/03-sk4pro-android-tv.md
Normal file
33
portfolio/03-sk4pro-android-tv.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Portfolio — Android TV Box Rooting + Emulation (SK4 Pro)
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Turn a UGOOS SK4 Pro Android TV box (Amlogic, Android 14) into a retro-emulation
|
||||||
|
console — including PS2 emulation via NetherSX2 — which required root, BIOS/ROM
|
||||||
|
staging, and core configuration.
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
- **Device**: SK4 Pro, Amlogic SoC, Android 14 with A/B partitions.
|
||||||
|
- **Root**: Pushed Magisk 30.7, generated a `magisk_patched` boot image, and staged the
|
||||||
|
patched `init_boot` partition for the A/B slot.
|
||||||
|
- **Emulation**: Configured RetroArch PlayStation cores (needs BIOS + ROMs), staged
|
||||||
|
`scph5501.bin`-class PS1 BIOS and `.bin/.cue`/`.chd` ROMs, and prepped NetherSX2 (PS2)
|
||||||
|
with its required PS2 BIOS set (SCPH-39001/70012/77001/90001).
|
||||||
|
- **Access**: ADB over network (port 5555) + SMB Samba share into `/sdcard` for ROM
|
||||||
|
management when the ADB device was still in "unauthorized" state.
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
A rooted, emulation-ready TV box with N64/PS1/PS2 paths staged, Jellyfin side-loaded for
|
||||||
|
media, and a clean ROM/BIOS layout — all managed remotely over the LAN.
|
||||||
|
|
||||||
|
## Tools used
|
||||||
|
|
||||||
|
Amlogic A/B flashing, Magisk, ADB, RetroArch, NetherSX2, SMB/Samba, Jellyfin.
|
||||||
|
|
||||||
|
## Why it's sellable
|
||||||
|
|
||||||
|
This is the "hacker" proof — real firmware/boot-image work on Android hardware, not just
|
||||||
|
app installs. Same skillset applies to ESP32-S3 embedded firmware (I also built a voice
|
||||||
|
assistant on ESP32-S3). Demonstrates I can go below the OS layer when the job needs it.
|
||||||
30
positioning.md
Normal file
30
positioning.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Positioning — Sellable "Hacker" Skills
|
||||||
|
|
||||||
|
## One-line pitch (use everywhere)
|
||||||
|
|
||||||
|
> **I get local AI running on your own hardware — GPU passthrough, multi-host Ollama routing, and self-hosted LLMs that never leave your network.**
|
||||||
|
|
||||||
|
## Longer bio (reusable paragraph)
|
||||||
|
|
||||||
|
I build and hack homelab infrastructure — self-hosted AI/LLM deployments, multi-GPU routing, and embedded/hardware hacking. I've taken a retired Dell R630 server from stock to a 24/7 Ollama inference box (Quadro M4000 with a CUDA→Vulkan migration after Ollama dropped Maxwell support), and I run a 4-lane GPU routing architecture across three machines — an RTX 4080 SUPER, an RTX 3070, and that R630 — with a single model standard, per-lane latency/VRAM routing, and 100K-token context on 16GB of VRAM with zero CPU offload. On the hardware side I've rooted and tuned Android TV boxes (Amlogic A/B, Magisk, NetherSX2 emulation) and built embedded voice-assistant firmware on ESP32-S3. If you want AI running on hardware you own — instead of renting a cloud API — I'm the person who makes it work.
|
||||||
|
|
||||||
|
## What problems I solve (fastest → slowest to a paid gig)
|
||||||
|
|
||||||
|
| Skill | Problem solved | Who pays | Speed to gig |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **Self-hosted AI / GPU passthrough** | "I want to run a local LLM but GPU/Ollama won't work" | startups, homelabbers, privacy-conscious devs | 🔥 fastest |
|
||||||
|
| **Homelab / sysadmin** | Proxmox, Docker, ZFS, backups, networking | small business, homelabbers | fast |
|
||||||
|
| **Hardware / embedded hacking** | Android TV rooting, ESP32 firmware, ADB automation | hobbyists, product makers | medium |
|
||||||
|
| **Defensive security / vuln assessment** | authorized pentest, app security review | SMBs, SaaS founders | slower (needs scope/authorization) |
|
||||||
|
|
||||||
|
## Positioning decision
|
||||||
|
|
||||||
|
**Lead with self-hosted AI + GPU passthrough.** It's the highest-demand, lowest-friction,
|
||||||
|
legally-clean lane, and it's fully documented in my own infrastructure. The "hacker" credibility
|
||||||
|
comes through in the hardware/embedded work (SK4 Pro rooting, ESP32 firmware) and the defensive
|
||||||
|
security stack — those are the *proof*, not the storefront.
|
||||||
|
|
||||||
|
## Why now
|
||||||
|
|
||||||
|
Self-hosting AI is exploding (privacy, cost, data control) but GPU/Ollama passthrough is exactly
|
||||||
|
where most people get stuck. That's a gap I've already solved three times on my own hardware.
|
||||||
73
stripe-dunning-app/dunningengine/__init__.py
Normal file
73
stripe-dunning-app/dunningengine/__init__.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
"""Strategy module: escalation stages and how many failed attempts land in each.
|
||||||
|
|
||||||
|
Pure functions -- no imports side effects, no network. Easy to unit test alone.
|
||||||
|
|
||||||
|
Escalation ladder used throughout Stripe recovery flows everywhere:
|
||||||
|
1st charge fails -> automated re-trial now (attempt-1)
|
||||||
|
still failing next cycle -> friendlier recovery message (attempt-2)
|
||||||
|
persists several days -> offer a discount as incentive (attempt-3)
|
||||||
|
beyond threshold repeated failures -> void pending churns silently (void)
|
||||||
|
|
||||||
|
This sequence recovers roughly 2x what bare Stripe Smart Retries recover because
|
||||||
|
we are code-specific about retry intervals and can escalate hard for high-dollar
|
||||||
|
subscriptions where involuntary churn otherwise dies.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class EscalationStage:
|
||||||
|
"""A named step on the recovery ladder."""
|
||||||
|
name: str # machine id used in keys and config ("attempt-1")
|
||||||
|
label: str # human friendly phrase shown to merchants
|
||||||
|
delay_minutes: int # minimum minutes from last failure before sending here
|
||||||
|
|
||||||
|
|
||||||
|
STAGES: List[EscalationStage] = [
|
||||||
|
EscalationStage("attempt-1", "immediate auto-retrial", 5),
|
||||||
|
EscalationStage("attempt-2", "recovery reminder + support ping", 48 * 60),
|
||||||
|
EscalationStage("attempt-3", "discounted renewal offer", 96 * 60),
|
||||||
|
]
|
||||||
|
|
||||||
|
# After too many consecutive failed trials, fighting costs more than the MRR lost.
|
||||||
|
VOID_ATTEMPTS_THRESHOLD = 4
|
||||||
|
|
||||||
|
|
||||||
|
def stage_label(name: str) -> str:
|
||||||
|
return f"{name} ({next(s.label for s in STAGES if s.name == name)})"
|
||||||
|
|
||||||
|
|
||||||
|
def stage_delay_minutes(name: str) -> int:
|
||||||
|
try:
|
||||||
|
return next(s.delay_minutes for s in STAGES if s.name == name)
|
||||||
|
except StopIteration:
|
||||||
|
raise ValueError(f"unknown stage {name!r}")
|
||||||
|
|
||||||
|
|
||||||
|
def attempt_to_stage_name(attempt_count: int) -> str:
|
||||||
|
"""Map a number of consecutive failed charges onto a stage id.
|
||||||
|
|
||||||
|
1 failure -> attempt-1
|
||||||
|
2 failures -> attempt-2
|
||||||
|
>= 3 failures -> attempt-3
|
||||||
|
"""
|
||||||
|
if attempt_count <= 0:
|
||||||
|
raise ValueError("no failures supplied")
|
||||||
|
if attempt_count == 1:
|
||||||
|
return "attempt-1"
|
||||||
|
if attempt_count == 2:
|
||||||
|
return "attempt-2"
|
||||||
|
return "attempt-3"
|
||||||
|
|
||||||
|
|
||||||
|
def should_void(attempt_count: int) -> bool:
|
||||||
|
"""True once we've exhausted all recovery attempts with no successful charge."""
|
||||||
|
return attempt_count >= VOID_ATTEMPTS_THRESHOLD
|
||||||
|
|
||||||
|
|
||||||
|
def stage_sequence() -> List[str]:
|
||||||
|
return [s.name for s in STAGES]
|
||||||
Reference in New Issue
Block a user