Built with Chinaski

How It Works

Chinaski is a two-piece system with a simple loop: edit while the CMS is private, build on demand, and serve what it wrote. This page is the whole lifecycle in one place.

The loop

  1. Edit: sign in to the admin and manage everything: pages, posts, blocks, snippets, categories, redirects, navigation, forms, and media. It all lives in one SQLite database file.
  2. Build: a separate builder process reads that database, renders your templates, and writes the finished site as static files to disk. Building happens only when you or a webhook says so; it’s an explicit operation with a proper log, not a background side effect.
  3. Serve: your web server hands out the built files straight from disk. Visitors never touch the CMS application or the database.
  4. Repeat: change content, click Build (or let a webhook or the API trigger it), and the next static copy swaps into place.

Why this shape

  • The admin is private. In production the CMS binds only to a loopback address; internet visitors reach the static host, never the writer app or database.
  • The public site has no application to attack. What ships is a plain directory of HTML and assets. There is no per-request rendering and no runtime JavaScript: nothing to inject into, nothing to leak, and a tracker has no mechanism to exist.
  • One process’s failure can’t touch the other’s. A failed build never takes the site down: output is written to a staging directory and renamed into place only when complete; otherwise the previous build keeps serving.

What visitors actually get

  • Pages and posts as index.html files at their own paths, per language, under each language’s subtree of output/
  • Shared assets synced into every language tree (media, fonts, favicons)
  • Per-language generated files: sitemap, search index, RSS feed, robots.txt, error page, and PWA extras when enabled
  • Redirect pages and a generated server redirect config, so retired URLs keep working

The two deliberate exceptions to “fully static”

  • Public forms (contact/newsletter blocks) submit to /forms/<slug>, which the stock server config proxies to the CMS so submissions can be stored.
  • Password-gated pages ship their content inside the HTML and unlock in the visitor’s browser: a client-side check, clearly a trade-off for page-level privacy on a static site.

Under the hood

  • One instance = one SQLite file. No database server, no credentials, no network between app and storage. Multiple instances (per client, per stage) each carry their own file.
  • Upgrading is bring-the-service-up: each startup runs idempotent schema migrations automatically, so instances self-heal their schema on the way.
  • Integrity is provable: each build signs a manifest held separately from the output, so the live site can be attested against the build that produced it.

Getting in on day one

A fresh install hands you a placeholder admin account: the browser is redirected to /setup on the first visit, where you set the real admin password (at least 12 characters with mixed case and a digit), choose UI content and initial languages, and set the site URL. After that, the setup page disappears, and the loop above is yours to drive.

Read this site in