Skip to content
RogerSquarePublic

About

SSH-accessible terminal portfolio built with Go, Bubble Tea, and Lip Gloss

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Cairn

SSH-accessible terminal portfolio in Go — ssh r-that.com and you're in a TUI, no browser required.

Live demo: ssh r-that.com (or clone and go run .) Stack: Go · Bubble Tea (TUI) · Lip Gloss (styling) · Wish (SSH server) · go:embed Status: Active

What's interesting technically

The entire portfolio is one Go binary. Content lives in a single data.json that's embedded into the compiled executable at build time via Go's embed package — no filesystem access at runtime, no config deploys, no CDN. The SSH server (Wish) lets anyone view the portfolio without needing a browser or installing anything; they just ssh in and get an MVU-architected TUI. Deploy is a single binary push. Tradeoff: data.json updates require a rebuild, but that's the same lifecycle as any compiled application — and it means content integrity matches binary version exactly.

(formerly Terminal Portfolio)

An interactive, SSH-accessible terminal portfolio built with Go and the Charm Bracelet ecosystem. It renders a full TUI (terminal user interface) with multiple navigable views, styled layouts, and keyboard-driven navigation. Connect via SSH or run the binary directly to browse through About, Skills, Projects, Experience, Education, and Contact sections.

Overview

This project uses the MVU (Model-View-Update) architecture provided by Bubble Tea. All portfolio content is defined in a single data.json file, which gets embedded into the compiled binary at build time using Go's embed package. The SSH server is powered by Wish, allowing anyone to view the portfolio by connecting to your server without needing a browser.

Model (state) --> View (render) --> Update (handle input) --> Model

Features

  • SSH server: Serve the portfolio over SSH on port 2222 using Wish
  • Rich TUI design: Styled borders, colors, and layouts using Lip Gloss
  • Keyboard navigation: Vim-style controls (h/j/k/l) and arrow keys
  • Six views: About, Skills, Projects, Experience, Education, Contact
  • Responsive layout: Adapts to terminal resize events, with separate layouts for wide and narrow terminals
  • Loading animation: Spinner on startup before rendering content
  • Data-driven content: All portfolio data loaded from a single JSON file
  • Cross-platform binaries: GitHub Actions workflow cross-compiles for Windows, macOS, and Linux (amd64 + arm64)

Tech Stack

Component Technology
Language Go 1.25
TUI Framework Bubble Tea
Styling Lip Gloss
Components Bubbles (viewport, spinner)
SSH Server Wish
Logging Charm Log
Rate Limiting golang.org/x/time
CI/CD GitHub Actions (cross-compile on tag push)

Getting Started

Prerequisites

  • Go 1.21+
  • A terminal with color and Unicode support

Build and Run

git clone https://github.com/RogerSquare/cairn.git
cd cairn
go mod download
go build -o cairn
./cairn

The SSH server starts on port 2222. Connect from another terminal:

ssh localhost -p 2222

Keyboard Controls

Key Action
q / Ctrl+C Quit
] / Right Arrow Next view
[ / Left Arrow Previous view
+ / = Jump to Home (About)
k / Up Arrow Scroll up
j / Down Arrow Scroll down

Customization

Edit data.json in the project root to change the portfolio content:

{
  "Contact": {
    "Name": "Your Name",
    "Title": "Your Title",
    "Email": "you@email.com"
  },
  "About": "Your bio here",
  "Skills": [],
  "Projects": [],
  "Experience": [],
  "Education": []
}

The file is embedded into the binary at compile time, so rebuild after making changes.

Deployment

The live site at r-that.com runs the TypeScript/Ink version in the ts/ directory on a Hostinger VPS — not the Go binary above. This section is specifically for pushing updates to that live deployment.

VPS layout

Thing Value
Repo path on VPS /opt/portfolio (run from /opt/portfolio/ts)
Mutable data dir /var/lib/cairn (via CAIRN_DATA_DIR env var)
SSH portfolio service portfolio.service (port 22)
Web server service portfolio-web.service (listens on :3000, behind nginx)
Reverse proxy nginx — r-that.com -> localhost:3000, wiki.r-that.com -> /var/www/wiki
Admin SSH ssh -p 2200 <user>@r-that.com
DNS Cloudflare A records -> VPS IP, proxy enabled (TLS termination at Cloudflare)

Runtime-mutable content (data.json, blog posts) lives outside the repo at $CAIRN_DATA_DIR. That way admin CMS edits survive git pull. The repo only contains seed templates (ts/data.example.json, ts/posts.example/) which are copied on first boot if the target is empty.

Happy path deploy

After committing and pushing locally:

ssh -p 2200 <user>@r-that.com
cd /opt/portfolio && git pull
sudo systemctl restart portfolio.service portfolio-web.service

Three commands, done.

Common scenarios

Content edits (bio, skills, projects, blog posts): done through the admin UI at https://r-that.com/admin. Writes go to $CAIRN_DATA_DIR, not the repo — no deploy needed, changes are live immediately.

Seeding new content from the repo: if you want to push a new blog post through git instead of the admin UI, add the file to ts/posts.example/ and commit. It only gets copied to the live posts dir on first boot into an empty directory — it won't overwrite or merge with existing posts. For ongoing content, use the admin UI.

Changed ts/src/*.ts / .tsx: same 3 commands. Both services run via tsx (no compile step).

Changed ts/package.json (added a dependency): extra step before restarting:

cd /opt/portfolio/ts && npm install
sudo systemctl restart portfolio.service portfolio-web.service

Debugging a deploy

Tail live logs:

sudo journalctl -u portfolio.service -f
sudo journalctl -u portfolio-web.service -f

Check service status:

systemctl status portfolio.service portfolio-web.service

Rollback

If a deploy breaks the live site:

cd /opt/portfolio
git log --oneline -5                      # find the last good commit
git reset --hard <good-commit-sha>
sudo systemctl restart portfolio.service portfolio-web.service

If ssh r-that.com itself is broken, you can still reach the box on the admin port (ssh -p 2200) — that's the whole point of splitting them.

One-time migration (already done on r-that.com)

If you ever set up a fresh VPS, do this once to move mutable data out of the repo:

sudo mkdir -p /var/lib/cairn
sudo mv /opt/portfolio/ts/data.json /var/lib/cairn/
sudo mv /opt/portfolio/ts/posts /var/lib/cairn/
sudo chown -R <service-user>:<service-user> /var/lib/cairn

Then add the env var to both systemd units (systemctl edit portfolio.service and systemctl edit portfolio-web.service):

[Service]
Environment="CAIRN_DATA_DIR=/var/lib/cairn"

systemctl daemon-reload && systemctl restart portfolio.service portfolio-web.service.

CI/CD

A GitHub Actions workflow (.github/workflows/release.yml) triggers on tag pushes. It cross-compiles the application for six targets:

  • Windows (amd64, arm64)
  • macOS (amd64, arm64)
  • Linux (amd64, arm64)

Create a release by pushing a tag:

git tag v1.0.0
git push --tags

Compiled binaries are attached to the GitHub release automatically.

Dependencies

  • Bubble Tea -- Terminal UI framework (MVU pattern)
  • Lip Gloss -- Declarative terminal styling
  • Bubbles -- Pre-built TUI components (viewport, spinner)
  • Wish -- SSH server for Bubble Tea applications
  • Charm Log -- Structured logging

License

MIT

About

SSH-accessible terminal portfolio built with Go, Bubble Tea, and Lip Gloss

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages