SM071 lets a manager (and a delegated automated partner) author themes and layouts directly: edit the files over WebDAV, preview the result, then activate it through the control API with validation, a backup, and a clean back-out. It builds on the WebDAV endpoint and the preview feature.
Three capability flags gate this surface. Capabilities are granted to
groups (with tools/lazysite-users.pl group-set GROUP KEY on, or in
the manager Groups page) and a user holds whatever their groups grant:
manage_themesmanage_layoutslayout.tt breaks
every page, so the structural layer is a higher privilege than themes.
manage_configlazysite.conf keys exposed over the
control API (the theme:/layout: pointers are covered by the two
flags above; this adds the broader allowlist).
Themes and layouts live under lazysite/layouts/. Over WebDAV they follow
a per-object rule:
lazysite/ (auth, cache, config) stays denied.dav_scope confines content publishing only; it does not gate theme or
layout access, which is governed by the capability flags above.
Every file under lazysite/layouts/ carries an lzs:sha256 property on
PROPFIND, and the control API's artifact-manifest returns the full
per-file manifest plus a combined digest. A client uses these to know
exactly what changed and to detect drift before activating.
Activation flips the theme: (or layout:) pointer in lazysite.conf
and clears the page cache, but only after:
theme.json declaring the active layout
in its layouts[]; a layout needs a layout.tt that compiles.digest as base; if
the artifact drifted since, activation returns a 409 conflict.The outgoing live theme/layout is snapshotted as
<name>-backup-<UTCstamp> (itself a selectable theme), pruned to
backup_retention (default 3). Back-out is just activating the
backup - or any earlier one.
A partner is an ordinary sub-user driven by an access token rather than a password. Provision one in a single step:
tools/lazysite-users.pl partner-create NAME --by PARENT \
[--layouts] [--config] [--scope /path]
This creates the sub-user with partner defaults (webdav +
manage_themes), records provenance (created_by/managed_by = the
creating user), mints a one-time pairing key, and prints an onboarding
brief to hand to the partner.
The partner exchanges the single-use pairing key for a short-lived access
token (token-exchange), presents it as HTTP Basic auth, and rotates it
before expiry (token-rotate). A leaked token self-expires; a spent
pairing key is dead. Disabling the account (account-disable, optionally
--cascade) revokes access everywhere immediately.
Both the WebDAV endpoint and the control API throttle per token with a
token bucket (default burst 200, refill 20/s). When throttled they return
429; a held lock returns 423. Both responses carry a
Retry-After header - honour it, backing off with a little jitter.
The control API is the manager API reached with token auth
(Authorization: Basic <user>:<lzs_ token>); it is CSRF-exempt for token
requests and confined to the control-API action set, each gated by the
capability above:
artifact-manifest, artifact-validatetheme-activate, layout-activatebase for the 409
conflict check; layout-activate accepts theme for the compatible
pair).
token-exchange, token-rotateaccount-create, account-disable, account-enable, account-reassign