Open Source the Engine
Not the Content
I wanted to open source my blog. Not the posts. Those are mine. But the framework: the theme, the build system, the AI chat integration, the WebGL effects that make this site run like a small GPU stress test. Everything that makes it work, without everything I’ve written on it.
The problem with most blog repos is that they’re one giant pile. Theme files, config, posts, assets, that draft you forgot about with your unhinged 3am takes. Want to share your setup? You either scrub every personal thing out of the history (good luck), keep it private (nobody benefits), or fork yourself and watch the repos drift apart forever.
The content and the container are fundamentally different things. Different audiences, different privacy needs, different update cadences. Split them.
The Split
My blog is now two repositories:
ellyseum.github.io (public)
- Jekyll templates and layouts
- CSS architecture (PostCSS, 22 split files)
- TypeScript frontend (Vite, xterm.js terminal)
- WebGL shader effects
- AI chat integration (Cloudflare Worker, RAG)
- GitHub Actions for build/deploy
- Everything someone would need to run their own version
ellyseum-content (private)
- All my blog posts
- The
site.ymlconfig with my personal info - System prompts for the AI chat
- RAG context chunks
- Drafts, unpublished ideas, notes, the spicy stuff
The public repo has a content/ directory in .gitignore. Clone it, you get a working blog with placeholder content. Swap in your own content repo, push, done.
The Webhook Glue
Here’s the clever part: how does the blog rebuild when I push to the content repo?
GitHub Actions supports repository_dispatch events. The content repo has a workflow that fires on push:
on:
push:
branches: [main]
jobs:
trigger:
runs-on: ubuntu-latest
steps:
- name: Trigger blog rebuild
run: |
curl -X POST \
-H "Authorization: token $" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/ellyseum/ellyseum.github.io/dispatches \
-d '{"event_type": "content-update"}'
The main blog repo listens for this event:
on:
repository_dispatch:
types: [content-update]
Push a post to the content repo, webhook fires, blog rebuilds with fresh content. I never manually deploy. I haven’t touched the deploy button in months. As it should be.
The Build Process
The blog’s build workflow:
- Clone the main repo (public, the framework)
- Clone the content repo into
content/(private, using a PAT) - Merge them: content files land where the framework expects them
- Build: Jekyll processes everything
- Deploy: push to GitHub Pages
The content repo is never in the public repo’s history. It’s cloned fresh at build time, used, discarded. The deployed site has the content, but the source repo doesn’t.
What You Can Fork
If you fork ellyseum.github.io, you get:
- A complete Jekyll blog with dark mode, responsive design
- TypeScript frontend with terminal emulator
- AI chat (bring your own Anthropic key)
- WebGL background effects
- RSS, sitemap, SEO tags
- GitHub Actions CI/CD
What you don’t get: any of my posts, personal config, or AI prompts. The repo is a framework, not a diary.
To make it yours:
- Create your own content repo (public or private, your call)
- Add your
site.ymlwith your info - Add your posts
- Set up the secrets (
CONTENT_REPO,CONTENT_PAT) - Push
The webhook setup takes maybe 10 minutes. After that, you push posts and they appear. That’s it. No deploy button, no manual steps, no babysitting.
Why This Matters
Open source thrives on separation of concerns. Library code vs application code. Framework vs implementation. Engine vs content.
Most personal blogs blur these lines because it’s easier. One repo, everything together, who cares. But if you’ve built something worth sharing, the blur becomes a barrier. You either share nothing or overshare.
Split the repos. Open source what’s reusable. Keep what’s personal. Let others benefit from your engineering without inheriting your opinions.
The engine is the interesting part anyway.