Skip to content

Latest commit

 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

postify

postify

The blog CMS where your content IS a git repository.

Self-hosted, single-binary, markdown-first. Every save is a commit — history, rollback, and diff come free.

Status: active development. postify runs www.fakih.net in production and is actively developed. Core authoring flows (web + mobile) work today, but APIs and UX are still evolving — expect occasional breaking changes before 1.0. Issues, ideas, and PRs are very welcome.


Why postify?

Most CMSes hide your content in a database. postify stores posts and pages as plain .md files in a git repository that acts as the storage and versioning engine:

  • No lock-in. Your writing is markdown in a repo you own. Point the config at any git repo and it just works.
  • Git history for free. Every save is a commit. git log is your edit history — roll back any change, diff any revision.
  • One deployable. A single Go binary embeds the admin SPA and serves the public site, the API, and /admin. No separate frontend server, no container layer, no database server to run.
  • Fast public reads. Rendered content is cached in SQLite; the public site never hits git per-request.
  • Blog-first, not dashboard-first. Public pages are server-rendered by Go — readers get fast HTML with per-page OG/meta tags and zero client JS required to read the site.

Features

  • Git as the content store — markdown posts (posts/), pages, drafts, and publish/unpublish via git mv
  • Server-rendered public sitehtml/template, per-page SEO/OG tags, generated /sitemap.xml and /robots.txt
  • WYSIWYG markdown admin — Svelte SPA with Milkdown editor, embedded via go:embed
  • Mobile authoring app — Flutter (iOS + Android) with one-step device pairing via QR code; supports multiple postify servers
  • Auth — JWT sessions, email + password, and Google OAuth; single-admin by design
  • Image storage with backup — primary GitHub assets repo (CDN-served) + Cloudflare R2 dual-write, with background retry
  • SEO ready — canonical URLs, OG tags, sitemap, robots, noindex preview links for drafts
  • Preview links — stateless, HMAC-signed short-lived links to render drafts through the live template

Architecture

             ┌──────────────────────────────┐
             │        Go binary             │
  visitor →  │  html/template (public SSR)  │
  admin  →   │  embedded Svelte (/admin)    │
  mobile →   │  Gin API (/api/v1)           │
             │  SQLite (cache, auth, dev.)  │── git repo (content .md)
             └──────────────────────────────┘

Content lives in a git repository; SQLite caches parsed/rendered content and holds auth/session/paired-device state; the web admin SPA is built and embedded into the binary.

Tech stack

Layer Choice
Backend Go + Gin, go-git, SQLite (modernc.org/sqlite), JWT
Web Svelte + Tailwind CSS + Milkdown (admin only, embedded)
Mobile Flutter (iOS + Android), same backend API
Content Git repo of .md files — the database
Images GitHub assets repo (CDN) + Cloudflare R2 backup

Quick start

Backend

Requires Go 1.2x and a content git repo.

cp .env.example .env        # set JWT_SECRET, CONTENT_REPO_PATH, etc.
make build                  # embeds web/dist + builds bin/postify
make run-all                # build everything, then run on :3030 (DEV=true)

First run: open http://localhost:3030 and complete the one-time /setup wizard.

Web admin SPA (development)

make web-install            # install web dependencies (uses $BUN, default bun)
make web                    # build SPA + public CSS into web/dist
make web-copy               # copy web/dist into internal/webadmin/dist

Mobile app

Flutter app for authoring on the go — device-pairing onboarding, multi-server support.

cd mobile
make get
make run-ios                # or: make run-android

Configuration

All backend config is environment variables — see .env.example. Key ones:

Var Purpose
PORT HTTP port (default 3030)
DOMAIN Canonical URL/OG domain
PUBLIC_URL Base URL included in device-pairing QR
JWT_SECRET HS256 signing key (>= 32 chars)
ADMIN_EMAIL Optional pre-seeded admin; empty = use /setup
ADMIN_PASSWORD_HASH bcrypt hash, not a raw password
SQLITE_PATH SQLite DB path
CONTENT_REPO_URL / CONTENT_REPO_PATH Content git repo
GITHUB_TOKEN / GITHUB_ASSETS_REPO Image storage (GitHub assets)
CLOUDFLARE_R2_* Image storage backup (R2, dual-written)

Commands

See make help (backend) and mobile/Makefile (mobile).

Repo layout

cmd/server/        Go entrypoint
internal/          backend: handlers, modules (post/page/settings/media/auth/device), web (public SSR), webadmin (embedded SPA)
web/               Svelte admin SPA (bun), Tailwind, public CSS
mobile/            Flutter app (iOS + Android), own Makefile
media/             local dev uploads (production uses GitHub assets/R2)
plan/              PRD/TRD/design docs

Docs

Design and planning live in plan/: PRD, TRD (architecture/stack decisions), per-module docs under plan/backend, plan/web, plan/mobile, plan/infra.

Roadmap (toward 1.0)

  • Stabilize the public API contract and version it cleanly
  • Hardening pass: auth, pairing, and preview-link edge cases
  • Docs site with a guided self-hosting walkthrough

License

MIT

About

The blog CMS where your content IS a git repository. Self-hosted, single-binary, markdown-first. Every save is a commit — history, rollback, and diff come free.

Resources

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages