You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An overengineered portfolio you visit over SSH instead of a browser.
ssh kherbin.getclingy.download
Demo
PortfolioSSH.mp4
Screenshots
Menu
About
Projects
Project README
Contact
Message
Grafana Metrics
How it works
SSH server — A Wish server listens on port 42069 (configurable via SSH_ADDRESS). It uses the host key mounted at SSH_HOST_KEY (auto-generated by charmbracelet/keygen if missing).
Session → TUI — Every incoming SSH session is handed to a Bubbletea program through the wish/bubbletea middleware, running in alt-screen mode. No shell access is ever granted — visitors only get the UI.
Screens — The TUI has five screens rendered with Lipgloss in forced TrueColor: Menu → About / Projects / Contact, plus a README view per project.
Live READMEs — Each project lists a GitHub repo URL. On startup (and re-polled every 5 minutes by a background goroutine), the raw README.md of every repo is fetched from raw.githubusercontent.com. The markdown is parsed with Goldmark (GFM extensions) and converted into styled TUI lines — headings, paragraphs, lists, code blocks, tables, and horizontal rules all render natively in the terminal.
Search — Vim-style search on content screens: press / to type a query, n/N to jump between matches, with matches highlighted live as you type.
Visitor messaging — The Contact screen doubles as a message form (name, email, message). Submissions are rate-limited per client IP (in-memory, 5-minute window), persisted to a SQLite database, and fire a Telegram notification — messages reach me without ever exposing an inbox.
Metrics — Each new session increments a Prometheus counter (portfolio_ssh_sessions), exposed on :8080/metrics.
Controls
Key
Action
↑/↓ or j/k
Navigate / scroll
Enter
Select (menu item / project)
Ctrl+D / Ctrl+U
Half-page scroll down / up
/
Search (then n / N for next / previous match)
?
Toggle help footer
b / Esc
Back
q
Quit / disconnect
Project structure
PortfolioSSH/
├── cmd/portfolio/ # Entrypoint — env loading, wiring, background jobs
├── internal/
│ ├── ssh/ # Wish SSH server, session → Bubbletea middleware
│ ├── ui/ # Bubbletea model + the five screens
│ │ ├── model.go # root model & state machine
│ │ ├── menu_view.go # main menu
│ │ ├── about_view.go # about screen
│ │ ├── project_view.go # project list
│ │ ├── readme_view.go # live-rendered project README
│ │ ├── contact_view.go # contact + message form
│ │ ├── input.go # text-input handling
│ │ ├── search.go # vim-style search
│ │ ├── github_fetch.go # README polling (every 5 min)
│ │ └── styles.go / layout.go # Lipgloss styling & layout
│ ├── database/ # SQLite message store (WAL mode)
│ ├── notifier/ # Telegram notification on new messages
│ ├── ratelimiter/ # In-memory per-IP rate limiting
│ └── metrics/ # Prometheus counter + /metrics endpoint
├── docs/screenshots/ # Screenshots used in this README
├── monitoring/prometheus/ # Prometheus scrape config
├── nginx/ # Nginx reverse proxy (Grafana on :80)
├── terraform/ # GCP infra — e2-micro VM, firewall
├── .github/workflows/ # CI/CD — build → GHCR → SSH deploy
├── Dockerfile # Multi-stage build (Go → Alpine, non-root)
├── docker-compose.yaml # Production stack (5 services)
└── docker-local.yaml # Local dev stack
nginx — the only other public port (80), reverse-proxying to Grafana
Configuration
Environment variable
Used by
Purpose
Default
SSH_ADDRESS
App
Listen address of the SSH server
:42069
SSH_HOST_KEY
App
Path to the SSH host key
./.docker-data/host_key
DATABASE_PATH
App
Path to the SQLite messages database
./messages.db
TELEGRAM_BOT_TOKEN
App
Bot token for message notifications
— (optional)
TELEGRAM_CHAT_ID
App
Chat ID to notify on new messages
— (optional)
IMAGE_TAG
Compose
Image tag to pull (GHCR + commit SHA)
—
PUBLIC_IP
Compose
Grafana root URL / domain
—
A local .env file is loaded automatically (godotenv) — handy for the Telegram variables during development.
Run locally
docker compose -f docker-local.yaml up --build
Then connect from another terminal:
ssh localhost -p 42069
The local variant also exposes the monitoring stack directly: Grafana on :3000, Prometheus on :9090, and the app's metrics endpoint on :8080.
Messages submitted via the Contact screen are stored in SQLite. Telegram notifications are optional — set TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID in a .env file to enable them.
Metrics
Metric
Type
Description
portfolio_ssh_sessions
Counter
Total number of SSH sessions served
portfolio_messages_received
Counter
Total number of succesful messages received
portfolio_messages_failed
Counter
Total number of messages failed
About
A Golang CLI portfolio deployed on a Google Cloud VM and managed with Terraform.