Multi-site

One lazysite instance serves many domains (SM110/SM151). Each domain is rooted at its own content subtree and is, to the outside world, a complete site - its own home page, navigation, theme, canonical URL, sitemap, feeds and search. Behind the scenes there is one docroot, one auth store, one manager and one MCP endpoint: one login and one AI credential drive every domain.

Configuration

Domains are declared in lazysite.conf: alias_hosts lists the served hosts, and alias.<host>.<key> lines override per host. The Domains manager page and the lazysite-domains CLI write the same lines.

site_name: Agency Home
alias_hosts: clienta.example, clientb.example

alias.clienta.example.site_name:    Client A
alias.clienta.example.content_root: sites/clienta
alias.clienta.example.site_url:     https://clienta.example
alias.clienta.example.theme:        harbour
alias.clienta.example.nav_file:     sites/clienta/nav.conf

Only a strict whitelist of presentation and routing keys may vary per host: site_name, theme, layout, nav_file, search_default, content_root, site_url, lang and lang_group. The Host header is request-supplied, so security-relevant keys (auth, manager, WebDAV, update) can never be overridden per host, and an undeclared or malformed host simply gets the base configuration.

Per-domain content roots

content_root names a docroot-relative directory that becomes the domain's /. The boundary is enforced at request time: the root must resolve (symlinks included) inside the docroot and never into the lazysite/ management tree. An invalid root degrades that one host to the docroot with a warning - it never takes the instance down or exposes a sibling's tree. A host declared without a content_root serves the default site.

Per-domain registries, search and cache

  • Sitemap, robots, feeds and llms.txt are generated per content root, written into that root, using the domain's own site_url
  • Search (scan: sources) is boxed to the requesting domain's root, so a search on one domain never returns another's pages
  • A host with no root of its own scans the whole docroot but skips every declared content root, so the default host never enumerates a client subtree
  • The render cache is host-keyed - each domain reads and writes its own cache slot, listed and cleared per host on the Cache page

The shared management plane

The lazysite/ tree (config, auth, ACLs, themes, layouts, logs, backups) exists once and is shared by every domain. Themes and layouts are reused across domains; the file browser sees every subtree; the first-party access log records the request Host so statistics can be split per domain. The design target is an agency: one user, one AI, N public sites - a single trust domain, not tenant isolation.

Web-server static rewrites

Rendered pages route through the processor, which also serves each domain's static files from its content root. For production efficiency the vhost tools generate per-Host rewrite rules from lazysite.conf, so the web server serves each domain's real static files (images, CSS, sitemap, feeds) directly from its subtree:

lazysite-apache-vhost rewrites --docroot /path/to/public_html
lazysite-nginx-vhost  rewrites --docroot /path/to/public_html

Apache gets a mod_rewrite block (fires only when the file exists); nginx gets a map $http_host plus a try_files line. Clean page URLs and the /lazysite, /cgi-bin and /manager surfaces are never rewritten - they still flow to the processor.

Notes

  • A bare docroot with no configured roots behaves exactly as a single-site install - multi-site is a pure superset
  • DNS, the web-server alias and TLS remain operator territory; lazysite owns only the conf and the content root
  • [% domain %] exposes the sanitised host to layouts, so one content root can still render differently per domain
  • Taking a client in-house is a move, not a rebuild: copy the subtree to a fresh instance and repoint DNS