Architecture
Chinaski is deliberately split into a few small moving parts instead of one big always-on server. This page explains the four pieces (the admin app, the builder, the static output, and the manager) and how they relate, so the rest of the documentation has a place to stand.
The admin app
The admin app (cms.pl) is the process you interact with: it serves the browser-based admin interface and the REST API. Everything you do as a user happens here: writing and editing content, managing users and forms, configuring settings, managing API keys and backups, and watching the translation and event logs.
It holds one SQLite database containing everything from page content to audit history, and it runs on your server only, bound to a loopback address behind your reverse proxy, never directly reachable by visitors. It’s a single, self-contained program (one Perl file plus its library modules), deployable in three ways: a systemd service in production, a development server while hacking, or a standalone server for offline deployments.
It also exposes the API endpoints that the tooling consumes (build status, batch jobs, translation coverage, settings, and the OpenAPI specification), so external tools (including AI assistants via the bundled MCP server) can drive the CMS without touching the browser interface.
The builder
The builder is a separate program that turns database content into the published site. It reads the same database, renders every page and post through templates once per configured language, and writes plain files to disk. Nothing about the builder is hidden: each build is an operation you or a webhook triggers explicitly, it logs its phases, records quality checks and results, and stores a full history of every run, with live progress visible in the admin dashboard while it runs.
The builder writes more than pages: every build regenerates each language’s sitemap, RSS feed, robots.txt, security-policy file, and redirect files: the complete published output of the site, deterministic from the database’s current contents.
Static output
The builder writes the site to disk as directories of plain HTML per language, plus the auxiliary files above. Your web server of choice (Caddy or nginx configurations ship with the project) serves those files directly to readers.
This split is the core architectural decision: readers never touch the CMS. The published site works whether the admin app is running or not, even if the whole CMS were offline, the site stays up. The only reader requests that reach the admin app are form submissions (forms are captured live by the CMS) and deliberately routed scanner probes to honeypot URLs. Caching at the proxy layer means rebuilt content reaches readers within minutes.
The manager
The docs also name a manager: a control panel to provision and run many sites from one place. It exists as its own product, Chinaski Manager (github.com/bigjohncobb/chinaski-manager), and pairs with the CMS on a single server running many instances.
What the Manager does:
- Tenants and instances. Create tenants, then under each tenant provision new Chinaski instances (default from the upstream repo), and run their full lifecycle: build, deploy, roll back to a previous version, start/stop, restart, and remove. Deployments run in stages (code fetch, dependencies, build, restart); if one stage fails, the manager remembers and can retry from that stage instead of starting over.
- Discovery. An existing server can be scanned for Chinaski instances the Manager doesn’t yet know: it looks for the systemd units and listens-and-works-in-the-right-directory signals, classifies what can be imported, and lets a superadmin import the found instances in one step.
- Domains. Attach admin and public domains to any instance, and the Caddy configuration is regenerated and reloaded automatically; each instance keeps its own port so upstreams stay unique.
- Backups. Per-instance database snapshots built with SQLite’s own safe snapshot command, with restore (which cleans the database’s shadow files so it opens cleanly), plus separate backup and restore of the Manager’s own control-panel database.
- Health checks. Background monitoring of instances (default: every minute), with optional webhook alerts and per-instance email alerts. The Manager itself exposes a health endpoint for external monitors and auto-restarts under systemd if it crashes.
- People and tokens. Three roles with tenant scoping: superadmin (everything, including tenant/user management and discovery), admin (one tenant’s instances), and user (own API tokens plus a view of their tenant’s audit log). API tokens come in read-only or full-access flavors, and there’s a small command-line client for both.
- Accountability. Every manager-level action is recorded in an audit log with who, what, and which tenant.
The Manager keeps its own separate database and secret: it’s a control-plane app, not something that lives inside your CMS instances.
Important distinction: the Manager controls many single-tenant CMS instances (one SQLite database per site). The “multi-tenant CMS with Postgres” in the main project’s roadmap is a different, unshipped thing: one CMS serving many tenants. Multi-site today = many instances, whether you manage them by hand with the shipped systemd patterns or through the Manager.
What exists in the main project today is exactly what the Discovery feature imports: single-instance systemd services where one unit pattern runs any number of sites (chinaski@1, chinaski@prod, chinaski@staging…), each with its own database, output directory, secret file, and backups.
The other shipped daemon: the MCP server
One more shipped component, and the third process you may actually run: chinaski-mcp, an adapter that lets AI assistants (Claude Code and any MCP-capable client) operate the CMS through the API. It speaks JSON-RPC over stdio, exposes the content CRUD/translation/bulk tools, keeps its own session store, and runs as its own systemd service when deployed. It is not the manager described above; it’s a driver for a single CMS instance. It ships because this codebase is developed AI-first, but it’s an optional tool: without it, the CMS works identically through the browser.
How a request flows
- Reader request: web server → static file on disk → done. The CMS is not involved.
- Form submission: web server → reverse-proxied
/forms/<slug>→ admin app → database (+ notification/webhooks). - Content edit: signed-in admin → admin app → database (with revisions and audit logged) → explicit rebuild → static files updated → readers see fresh content within the proxy’s short cache window.
- Fleet ops: Manager → instance systemd units, ports, and domains → provisioned/built/deployed CMS instances per tenant, with health and backups watched centrally.
The one-line summary
Four pieces: the admin app you edit in, the builder that publishes, the static files that serve, and the Manager, which ties many such sites together from one panel.