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
- 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.
- 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.
- Serve: your web server hands out the built files straight from disk. Visitors never touch the CMS application or the database.
- 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.htmlfiles at their own paths, per language, under each language’s subtree ofoutput/ - 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.