From d84002da33dc78c63837d3dd6406706edbcb96d0 Mon Sep 17 00:00:00 2001 From: senior Date: Tue, 28 Jul 2026 18:58:26 +0000 Subject: [PATCH] Replace project sections with live Services list - Add services.json: hand-maintained source of truth with 20 public homelab services across 7 categories (edge, observability, media, downloads, ethereum, ai, dev), each with name/url/desc/icon. - Rewrite index.html: Services section rendered client-side from services.json with a tiny up/down status probe (green dot = up, grey = down/unreachable; graceful fallback on CORS/offline). Keep a small Projects section linking the GitHub orgs so the old content is not lost. - Add services grid + status dot styles to styles.css. - Dockerfile: COPY services.json into the nginx html root. --- Dockerfile | 2 +- index.html | 274 ++++++++++++++++++-------------------------------- services.json | 73 ++++++++++++++ styles.css | 109 +++++++++++++++++++- 4 files changed, 282 insertions(+), 176 deletions(-) create mode 100644 services.json diff --git a/Dockerfile b/Dockerfile index 05081c7..9ebc30a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM nginx:1.27-alpine LABEL org.opencontainers.image.title="ffaerber/homepage" \ org.opencontainers.image.source="https://git.ffaerber.duckdns.org/ffaerber/homepage" -COPY index.html styles.css /usr/share/nginx/html/ +COPY index.html styles.css services.json /usr/share/nginx/html/ EXPOSE 80 diff --git a/index.html b/index.html index 8f16d19..15ca315 100644 --- a/index.html +++ b/index.html @@ -3,197 +3,40 @@ - - ffaerber — projects + + ffaerber — services
- -
+ +
-

CipherDolls — AI companion platform

-

What if an AI companion wasn't just an app, but something physical in your room? CipherDolls is my biggest project: an AI companion platform where characters can talk, listen — and live inside real devices.

- github.com/cipherdolls ↗ +

Services

+

All public-facing services from the homelab, grouped by what they do. A green dot means the service answered just now; grey means it is down or unreachable from your browser.

+
+
+

Loading services…

-
    - -
  • -

    core

    -

    The brain of the platform: one backend that handles chat, voice, memory, payments and the connection to the physical dolls.

    -
      -
    • Bun
    • Elysia
    • Prisma
    • PostgreSQL
    • Redis
    • MQTT
    • -
    -
  • - -
  • -

    server

    -

    A slimmed-down core for anyone who just wants to chat with a character — the same architecture, none of the complexity.

    -
      -
    • Bun
    • Elysia
    • Prisma
    • PostgreSQL
    • -
    -
  • - -
  • -

    webapp

    -

    The web face of CipherDolls — where you meet the characters.

    -
      -
    • React Router
    • TypeScript
    • Tailwind
    • -
    -
  • - -
  • -

    stream-player

    -

    Voices need to arrive instantly. This streams the AI's speech to your browser the moment it is generated.

    -
      -
    • TypeScript
    • Redis
    • WebSocket
    • -
    -
  • - -
  • -

    stream-recorder

    -

    The listening half: your voice streams in, gets converted on the fly, and lands with the AI.

    -
      -
    • TypeScript
    • WebSocket
    • ffmpeg
    • -
    -
  • - -
  • -

    firmware-watcher

    -

    The magic moment: a real device on your desk wakes up, connects, and speaks with the character's voice.

    -
      -
    • C
    • ESP-IDF
    • ESP32-S3
    • LVGL
    • -
    -
  • - -
  • -

    firmware-m5sticks3

    -

    The pocket version: the same companion on a tiny stick with a display and a microphone.

    -
      -
    • C
    • ESP-IDF
    • ESP32-S3
    • -
    -
  • - -
- -
+ +
-

Decentralized apps — Gnosis Chain + Ethereum Swarm

-

Can the web run without servers? These apps are my experiments in building things that nobody can switch off.

+

Projects

+

The code behind the services lives across a few GitHub orgs.

-
    - -
  • -

    SwarmChat

    -

    I was thinking about how cool it would be to have a web3-based chat system. Is it possible to communicate without any servers? With Swarm, a decentralised technology, this is now possible — messages travel encrypted through the network itself, and your wallet is your identity.

    -
      -
    • Solidity
    • React
    • Swarm PSS
    • WebRTC
    • -
    -
  • - -
  • -

    Pink Chainsaw

    -

    I wanted an imageboard where content can't just disappear. Images live on Swarm, and every post pays a little to keep them alive — good content earns you cheaper posting, bad content gets expensive.

    -
      -
    • Solidity
    • Foundry
    • React
    • Swarm
    • -
    -
  • - -
  • -

    FreeeMarket

    -

    Can you run a marketplace with no company behind it? Anyone opens a shop, buyers pay in stablecoins, and on-chain escrow replaces the middleman. Deliberately fee-free — a farmers' market for the internet.

    -
      -
    • Solidity
    • Gnosis Chain
    • Swarm
    • -
    -
  • - -
  • -

    WalletMatrix

    -

    My tokens were scattered across a dozen chains and I could never see the whole picture. So I built a pivot table for your wallet: tokens as rows, chains as columns — drag a cell from one chain to another to bridge.

    -
      -
    • React
    • TypeScript
    • Vite
    • -
    -
  • - -
  • -

    swarm-connect

    -

    Every Swarm dApp I built needed the same onboarding dance: wallet, network, Bee node, postage stamp. So I packaged it once — a single React button that walks users through all of it.

    -
      -
    • React
    • wagmi
    • npm package
    • -
    -
  • - -
  • -

    opentab

    -

    An experiment with onchain apps on Base.

    -
      -
    • Next.js
    • TypeScript
    • -
    -
  • - -
-
- - -
-
-

AI on GPU — self-hosted speech services

-

I wanted my projects to hear and speak without sending a single word to the cloud — so these run on my own GPU.

-
-
    - -
  • -

    whisper-cuda

    -

    Speech-to-text on my own GPU: no cloud, no per-minute pricing. Whisper packaged for the RTX 5090 so anything in my homelab can transcribe audio locally.

    -
      -
    • Python
    • FastAPI
    • CUDA
    • -
    -
  • - -
  • -

    kokoro-cuda

    -

    Giving my projects a voice: a small self-hosted TTS service with 49 voices in 9 languages, fast enough to stream. Type text, hear it instantly.

    -
      -
    • Python
    • FastAPI
    • CUDA
    • -
    -
  • - -
  • -

    voxtral-cuda

    -

    The newest open speech models are amazing but heavy. This runs Mistral's Voxtral on a local GPU, so state-of-the-art speech stays in my own house.

    -
      -
    • Python
    • vLLM
    • CUDA
    • -
    -
  • - -
-
- - -
-
-

Ethereum infrastructure

-
-
    - -
  • -

    stakingtracker

    -

    Tax season with an ETH validator used to mean paid APIs and spreadsheets. Now my own beacon node does the work: a small self-hosted app that records rewards daily and prints the tax report.

    -
      -
    • TypeScript
    • Fastify
    • SQLite
    • -
    -
  • - +
@@ -208,5 +51,88 @@ + + \ No newline at end of file diff --git a/services.json b/services.json new file mode 100644 index 0000000..99eb639 --- /dev/null +++ b/services.json @@ -0,0 +1,73 @@ +{ + "categories": [ + { + "name": "edge", + "label": "Edge & proxy", + "icon": "🌐", + "services": [ + { "name": "Traefik", "url": "https://ffaerber.duckdns.org/dashboard/", "desc": "Reverse proxy and edge router for the whole homelab.", "icon": "🛡️" } + ] + }, + { + "name": "observability", + "label": "Observability", + "icon": "📊", + "services": [ + { "name": "Grafana", "url": "https://grafana.ffaerber.duckdns.org", "desc": "Dashboards for metrics, logs and system health.", "icon": "📈" }, + { "name": "Prometheus", "url": "https://prometheus.ffaerber.duckdns.org", "desc": "Metrics collection and alerting.", "icon": "🔥" } + ] + }, + { + "name": "media", + "label": "Media", + "icon": "🎬", + "services": [ + { "name": "Jellyfin", "url": "https://jellyfin.ffaerber.duckdns.org", "desc": "Self-hosted movies, shows and music.", "icon": "🎞️" }, + { "name": "Immich", "url": "https://immich.ffaerber.duckdns.org", "desc": "Self-hosted photo and video library.", "icon": "📸" }, + { "name": "ComfyUI", "url": "https://comfyui.ffaerber.duckdns.org", "desc": "Node-based image generation UI on the GPU.", "icon": "🎨" } + ] + }, + { + "name": "downloads", + "label": "Downloads & files", + "icon": "💾", + "services": [ + { "name": "qBittorrent", "url": "https://bittorrent.ffaerber.duckdns.org", "desc": "Torrent downloads over VPN.", "icon": "🧲" }, + { "name": "Filebrowser", "url": "https://files.ffaerber.duckdns.org", "desc": "Web file browser for the homelab storage.", "icon": "📁" } + ] + }, + { + "name": "ethereum", + "label": "Ethereum", + "icon": " Ξ", + "services": [ + { "name": "Mainnet Geth", "url": "https://mainnet.ffaerber.duckdns.org", "desc": "Ethereum mainnet execution client (RPC).", "icon": "⛓️" }, + { "name": "Mainnet Beacon", "url": "https://beacon.ffaerber.duckdns.org", "desc": "Lighthouse consensus / beacon node.", "icon": "🗼" }, + { "name": "Staking Tracker", "url": "https://staking.ffaerber.duckdns.org", "desc": "Daily validator rewards and tax reports.", "icon": "💰" }, + { "name": "Gnosis Nethermind", "url": "https://gnosis.ffaerber.duckdns.org", "desc": "Gnosis Chain execution client (RPC).", "icon": "⛓️" }, + { "name": "Bee", "url": "https://bee.ffaerber.duckdns.org", "desc": "Ethereum Swarm node — decentralized storage API.", "icon": "🐝" }, + { "name": "Bee Dashboard", "url": "https://bee-dashboard.ffaerber.duckdns.org", "desc": "Web UI for the Swarm Bee node.", "icon": "🎛️" }, + { "name": "t4t Provider", "url": "https://t4t-provider.ffaerber.duckdns.org", "desc": "Truth-for-Truth data provider node.", "icon": "🔍" } + ] + }, + { + "name": "ai", + "label": "AI & speech", + "icon": "🤖", + "services": [ + { "name": "Whisper", "url": "https://whisper.ffaerber.duckdns.org", "desc": "Self-hosted speech-to-text on the GPU.", "icon": "👂" }, + { "name": "Kokoro", "url": "https://kokoro.ffaerber.duckdns.org", "desc": "Self-hosted text-to-speech, 49 voices.", "icon": "🗣️" }, + { "name": "Ollama", "url": "https://ollama.ffaerber.duckdns.org", "desc": "Local LLM serving on the GPU.", "icon": "🦙" }, + { "name": "Open WebUI", "url": "https://chat.ffaerber.duckdns.org", "desc": "Chat UI for Ollama models.", "icon": "💬" } + ] + }, + { + "name": "dev", + "label": "Dev platform", + "icon": "🛠️", + "services": [ + { "name": "Gitea", "url": "https://git.ffaerber.duckdns.org", "desc": "Self-hosted Git and CI for all ffaerber repos.", "icon": "🔧" } + ] + } + ] +} \ No newline at end of file diff --git a/styles.css b/styles.css index 658b945..a00e96a 100644 --- a/styles.css +++ b/styles.css @@ -113,7 +113,7 @@ body { line-height: 1.65; } -/* Tags — small, muted detail */ +/* Tags — small, muted detail (kept for any legacy use) */ .tags { list-style: none; margin: 0; @@ -134,6 +134,113 @@ body { color: var(--border); } +/* Services — grouped grid of homelab services */ +.services-root { margin-top: 0.5rem; } +.services-loading, .services-error { + color: var(--muted); + font-style: italic; +} +.service-group { margin: 0 0 2rem; } +.group-title { + font-size: 1.05rem; + font-weight: 700; + margin: 0 0 0.75rem; + letter-spacing: -0.01em; + border-bottom: 1px solid var(--border); + padding-bottom: 0.35rem; +} +.group-icon { margin-right: 0.4rem; } +.service-list { + list-style: none; + margin: 0; + padding: 0; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); + gap: 0.75rem; +} +.service { + border: 1px solid var(--border); + border-radius: 8px; + background: transparent; + transition: border-color 0.15s ease, background 0.15s ease; +} +.service:hover { border-color: var(--accent); } +.service-link { + display: flex; + align-items: center; + gap: 0.6rem; + padding: 0.7rem 0.85rem; + text-decoration: none; + color: var(--fg); + height: 100%; +} +.service-icon { + font-size: 1.4rem; + line-height: 1; + flex-shrink: 0; + width: 1.6rem; + text-align: center; +} +.service-body { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-width: 0; +} +.service-name { + font-weight: 700; + font-size: 0.98rem; + letter-spacing: -0.01em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.service-desc { + font-size: 0.82rem; + color: var(--muted); + line-height: 1.4; + margin-top: 0.1rem; +} + +/* Status dot — green up, grey down/pending */ +.status-dot { + width: 0.6rem; + height: 0.6rem; + border-radius: 50%; + flex-shrink: 0; + border: 1px solid var(--border); + background: var(--muted); + opacity: 0.5; + transition: background 0.3s ease, opacity 0.3s ease, border-color 0.3s ease; +} +.status-pending { background: var(--muted); opacity: 0.4; } +.status-up { + background: #2ea043; + border-color: #2ea043; + opacity: 1; +} +.status-down { background: #d73a49; border-color: #d73a49; opacity: 0.85; } + +/* Orgs list (Projects section) */ +.orgs { + list-style: none; + margin: 0; + padding: 0; + max-width: 42rem; +} +.orgs li { + padding: 0.7rem 0; + border-bottom: 1px solid var(--border); + font-size: 0.98rem; +} +.orgs li:last-child { border-bottom: none; } +.orgs a { + color: var(--accent); + text-decoration: none; + font-weight: 700; +} +.orgs a:hover { text-decoration: underline; } + /* Footer */ .site-footer { margin-top: 3rem;