Manager
Web-based admin and content manager for lazysite.
Overview
The lazysite manager is a web-based admin UI built into lazysite itself. It lets you configure the site, manage users, install themes, enable extensions, edit pages, and clear the cache - all from the browser.
Manager pages are ordinary lazysite .md pages served through the
normal pipeline, using a dedicated manager theme for consistent chrome.
Enabling the manager
Add to lazysite/lazysite.conf:
manager: enabled
manager_path: /manager
The manager is disabled by default. Access is the ui capability,
granted through a group on the Groups page (the seeded sysops
group carries it). Bootstrap in one command:
perl tools/lazysite-users.pl --docroot /path/to/public_html setup-sysop --user NAME
or, once the site has an operator, add a user to the admin group (the manager's Users page does the same):
perl tools/lazysite-users.pl --docroot /path/to/public_html \
group-add alice sysops
On a site with no operator, setup-sysop is the only way to the first manager
access. add followed by group-add alice manager makes an account in a group
that is merely called manager: it carries no capabilities and is not an
operator group, so the account can sign in and reach nothing in the manager. The
users tool prints a note saying so on that path.
(Capabilities on groups are the mechanism of record; the legacy
manager_groups: conf key is retired and migrates itself away on upgrade.)
Accessing the manager
Navigate to /manager (or the configured manager_path). You must be
authenticated and in a group carrying the ui capability. Unauthenticated
visitors are redirected to /login.
Pages
The sidebar is four groups, ordered by how often each page is wanted (SM957):
- Site - Submissions, Files, Navigation, and Visitor statistics when that extension is enabled: what an operator opens on an ordinary visit.
- Setup - Appearance, Extensions, Connectors, Data tables (when enabled) and Domains: set once, revisited when something changes.
- Access - Users, Groups, Sessions & keys.
- System - Site settings, Cache, Backups, Audit log.
A page the account cannot open is shown locked, with a padlock and the reason - never hidden - and for an account that can grant capabilities it links to the Groups page. The command palette (Jump to, Ctrl K) lists the same pages the menu links to. Handlers is not on the menu: it is reached from Extensions and from each form on Submissions.
Submissions
/manager/submissions - the first entry. Every form on the site with what it
has kept; choose one to see its rows, newest first, with the quarantine filter,
Confirm, Approve (on a registration form), Delete, bulk delete and CSV download.
Each row's Open shows that one submission at /manager/submission, the page
a submission notice links to, and each form's Handlers opens its card on the
Handlers page. Reading needs read_submissions; Confirm, Approve and Delete
need manage_forms as well.
The menu entry shows a count when the account holds notifications: its unread
submission notices, from the bell's own read state. Opening the page marks
nothing read; following a notice, or a row's Open, marks that notice read, and
the bell's Mark all read clears the count. The address the viewer used to live
at, /manager/plugin-config?submissions=<store>, redirects here.
Site settings
/manager/ (or /manager/config) - the Site settings item. Edit site
identity and review the extension registry.
- Site settings -
site_name,site_url, navigation file path,search_default, manager state, and manager path (manager access is granted on the Groups page, via theuicapability). Also here: backups to keep (per kind), the asset cache lifetime, the five service switches (Manager UI, MCP, OAuth, Control API, WebDAV) each with its "held by N groups / M accounts" line, pairing-key exchange and token rotation, the manager style, and the update channel. The active layout and theme are not shown on this page - they live on Appearance, reached from the sidebar. Saves tolazysite/lazysite.conf. - Extensions (
/manager/plugins, under Setup) - enable and disable the extensions; their configuration is its Configure page (see below).
Files
/manager/files. File browser for the docroot. You can:
- Navigate directories
- Open a page for editing (
/manager/edit?path=...) - Create new
.md,.url, and directory entries - Delete files (with confirmation)
- Review the site's alias redirects in the read-only Aliases card
(alias → target, with a 301/302 badge; aliases are authored in each
page's front matter via
aliases:/aliases_temp:) - Step through a file's version history (when the Content history extension is enabled): each file's expand card gains a History panel listing every recorded version (when, who, what) with View (that version's raw content, read-only), Diff (against the current file) and Restore. A restore is written back as a normal save, so it becomes the newest version itself - always reversible.
The editor at /manager/edit shows:
- Front matter form (title, subtitle, and whatever other keys the page carries) plus the raw YAML behind a collapsible Metadata panel
- Monospace editor for the page body
- Preview pane, refreshed on Save and by Reload preview - it shows the page as last saved, not as you type
- Save button (writes file and invalidates cache)
- History (when the Content history extension is enabled): opens this file's History panel in Files - the same View, Diff and Restore as above
- Collaborative edit lock - only one user can edit a file at a time
Locks expire after 5 minutes and are renewed automatically while the editor is open. Leaving with unsaved changes prompts a browser warning first; cancelling the warning keeps your edit lock, confirming releases it.
Nav
/manager/nav. Visual editor for lazysite/nav.conf:
- Drag and drop to reorder items
- Indent and outdent to change nesting (the arrows are disabled where the move is not legal)
- Edit an item's label and URL in a small dialog (the pencil on its row)
- A blank URL makes an item a group heading; give it a URL to make it a link again
Saves back to lazysite/nav.conf as YAML. Changes apply only on Save:
an "Unsaved changes" note appears next to the Save button as soon as you
modify anything, and the browser warns before you leave the page with
unsaved changes.
Extensions
One menu entry, Extensions, over two pages: /manager/plugins lists every
discovered extension (each plugins/*.pl that answers --describe) to enable
or disable, and its Configure page (/manager/plugin-config) configures the
enabled ones. Each links to the other, and both to Handlers.
On Configure, each enabled extension appears with a form generated from its
config_schema. Save writes the extension's config file (e.g.
lazysite/forms/smtp.conf for the SMTP extension).
Extensions that declare actions (e.g. Run audit) show action buttons
that invoke the extension and display the result. An action can also come back
asking for a decision - the Remote sync extension's Pull does this when the
same page changed both here and on the remote copy: the pages are listed and
you choose Keep mine or Take theirs (a safety snapshot is taken
first either way).
The Content history extension (content-history) turns on per-file
version history for the site content. Ticking the extension on Extensions
is all it takes: an initial snapshot of the current site is recorded, then
every save (manager, WebDAV, or AI connector) becomes a recorded version,
browsable per file on the Files page (History / Diff / Restore). Unticking
the extension pauses recording and keeps every recorded version. On Configure,
Status reports whether it is recording, how many versions are
recorded, and whether the host has the git package it needs, and History
overview summarises what is recorded; the tick on Extensions is the
one control that starts or pauses recording. The history covers the content plus lazysite.conf / nav.conf
and never includes secrets or personal data (accounts, form submissions,
logs), so it is safe to sync to a private remote; full-system backups (see
Backups) remain the disaster-recovery mechanism for exactly what the
history excludes.
The Remote sync extension (git-sync) keeps a copy of the site content on
a remote server. It needs the Content history extension to be enabled;
configure the remote address, branch and access token here, then use Test
connection, Push - send changes and Pull - fetch changes. Push
never overwrites changes on the remote that you don't have - Pull them
first.
Appearance
/manager/appearance (formerly "Themes"; /manager/themes redirects here).
Manage layouts and themes and switch the active pair.
- Layouts repo - the
layouts_reposetting, one field. The branch the catalogue is read from is thelayouts_refkey inlazysite.conf(defaultmain); it has no control on the page. - Browse the repo - the repo's
manifest.jsoncatalogue (click Refresh to load it): install a single layout and its theme(s) on demand, with version info. - Installed layouts & themes - the switcher: activating a layout or a theme sets the site default for every visitor and clears the page cache. Delete a layout (which removes its themes too, behind a confirm, and only when it is not active or in use by a domain); per theme, Preview on any non-active theme, and Activate / Delete for a non-active theme under the active layout. The served theme is read-only (SM749): Copy it, edit the copy, activate the copy - there is no rename. Preview works across layouts. A collapsed Backups panel collects the theme/layout snapshots taken automatically when a layout or theme is switched or deleted - safe to remove, individually or all at once (the active layout is never touched). A full-system backup (Backups page) also carries the current themes and layouts.
- Upload a theme zip (must contain
theme.jsonat the root with a non-emptylayouts[]array naming installed layouts).
The same per-layout operations are available to partners over the control API
and the MCP connector (layout-install / layout-delete / layouts-manifest;
install_layout(update:true) redeploys a changed layout).
Users
/manager/users. User accounts (the same data files as
tools/lazysite-users.pl):
- Add, remove, and rename users
- Set or clear passwords
- Assign each user to groups from its card
- A read-only capability grid (channel x action, derived from groups - capabilities are edited on the Groups page), sub-users, credentials, and onboarding
Selecting an account is separate from configuring it. The account list is a
tree: one line per account - its name, a human or AI tag, a (+N) count
of sub-users, disabled / expired where they apply, and an Edit button.
Clicking the name expands or collapses its sub-users, nested beneath it; the
parent is read from the nesting, so it is not repeated on the row. Editing is a
deliberate second step: press Edit to open that account's settings in a
full-width editor sheet whose head names the account and states what it is
(human · top-level account · 2 sub-users). The sheet is the same size and
position however deep the account sits in the tree, so nesting never shrinks it
and it is never ambiguous whose settings are on screen. Close it with Esc, the
× button, or a click outside.
Groups
/manager/groups (under Access in the menu). View, create, and delete
groups, and tick membership per group. Add group takes a name alone; a
group can be deleted only once it is empty (the row says Remove members to
delete until then). (Per-user assignment is also available on the Users
page.)
Address lists
/manager/address-lists (under Access). Named lists of addresses, each an
allow list or a deny list. An entry is a single address or a CIDR range
(IPv4 or IPv6), with a note saying whose it is. Anyone who holds the Users &
groups permission may read the lists; only the operator creates, edits or
deletes one. Every change is recorded in the audit log with the list and the
addresses added or removed.
A list does nothing by itself: it is applied by a rule that names it. Where an
allow list and a deny list both match an address, deny wins. An address that is
not a valid address, or a list the site cannot read, is refused where a rule
names a list - the rules fail closed. The same lists are read over the control
API (address-lists) and MCP (list_address_lists); writing them is the
manager's.
Sessions
/manager/sessions (under Access; needs the Users & groups
permission). Lists the live sessions - user, signed in, IP address, device,
with a marker on your own current session - and lets you:
- Sign out a single session (its cookie stops working immediately).
- Sign out everywhere for one user - all of that user's sessions, including any signed in before session listing existed.
- Invalidate all sessions at once by rotating the signing secret - every cookie, including yours, stops working and everyone signs in again.
Sessions are still signed cookies, not server-side records: the list is an advisory registry written at login (self-pruned after 24 hours), and revocations are enforced at cookie verification. Sessions from before this feature cannot be listed, but per-user sign-out and secret rotation still kill them. Revocations are recorded in the audit trail.
Backups
/manager/backups. Typed snapshot sections - Content backups (create,
download, in-app restore with an automatic pre-restore safety snapshot) and
Full-system backups (download only; restored by a system user with
lazysite backups --docroot D --restore-full FILE --domain NEW-DOMAIN - the
line the page prints - since they carry the auth secrets). Backups
are the disaster-recovery mechanism, including config and secrets;
day-to-day content versioning lives in the Content history extension
(Extensions, and its Configure page). Theme and layout snapshots are managed
on the Appearance page.
Cache
/manager/cache. The cached copies of rendered pages:
- Lists every cached copy with the domain it serves, its age and whether its source still exists
- Delete removes one copy, for that domain only; the page renders afresh on its next request
- Delete all removes every copy at once (useful after theme changes)
Audit
/manager/audit. The material-action trail (logins, edits, deletes,
config/theme changes, denied attempts) with who/what/when/where and the
outcome. Filter by user, target, or a From/To date range; each row records
the action's target (the page, the extension, nav, etc.). Operations run on
the shell with the users tool (setup-sysop, account and credential
changes) appear here too, with origin cli and the invoking system user;
installs and upgrades appear with origin install. Browsing analytics
live separately in Visitor statistics, not here. Viewing the audit trail
requires the Audit trail permission - its own capability, separate from
Analytics - granted through a group on the Groups page; it is read through an
append-only cache, so only newly-appended lines are parsed on each load.
Switching the trail off. An instance may run without an audit trail, and
the trail can be switched off and on again with audit-trail-set
({"state": "off", "reason": "..."}). It needs Site config and the audit
trail switch permission together - the second lives in a group of its own,
Capability: audit trail switch, which no role holds by default, so nobody can
silence the trail until an administrator decides somebody should. The switch
is itself recorded: the switch-off is written to the trail, naming who and why,
before recording stops, and the switch-on as it resumes, so the gap has two
named edges. Off stops recording and keeps everything already recorded.
Visitor statistics
/manager/stats. A read-only dashboard from lazysite's own first-party access
log: the site records its traffic itself (always anonymised at write - a
daily-salted visitor key, never the IP), so statistics work out of the box
with no web-server setup. The web-server access log is the fallback source
when no first-party data exists. Because
lazysite uses no cookies or JS, it classifies traffic by log-only heuristics into
real people, the logged-in operator, AI assistants, bots and probe noise (each
reported separately), splits referrers into external / internal / direct, links
top pages to the live page, and shows per-day counts over a configurable window.
If a server error log is readable (auto-detected),
it also shows a synthesised summary of recent server errors (categories and
counts only - never raw lines, addresses or paths). It never exposes any log
file's path, and the raw logs are not downloadable through the manager.
Provided by the opt-in
Visitor Statistics
extension: the nav item appears only when the extension is enabled - enable it on Extension
Manager (recording and retention are tunable under Extensions -> Configure). An AI connector granted
the Analytics permission can analyse the same data for trends via the
analyse_visitors tool, getting only the aggregated, IP-anonymised figures (see
AI connector tools and
AI briefing - visitor analytics).
Admin bar on site pages
When the manager is enabled, the processor injects an admin bar on
site pages (non-manager pages) for authenticated users with manager
access (the ui capability). The bar shows:
- Manage - link to
/manager/ - Edit - link to the editor for the current page
- Sign out
- Warning when the user has no password set
The admin bar sits in normal page flow at the top (it scrolls with the page, so it never overlaps a theme's own sticky header). Unauthenticated visitors and non-manager users do not see it.
Installation
The installer copies lazysite-manager-api.pl to cgi-bin/ alongside
lazysite-processor.pl. The manager pages in starter/manager/ are
served as regular lazysite pages; the manager's internal template in
starter/lazysite/manager/ (D013: outside both layouts/ and
themes/) supplies its chrome.
For manual installation:
cp lazysite-manager-api.pl /path/to/cgi-bin/
chmod 755 /path/to/cgi-bin/lazysite-manager-api.pl
cp -r starter/manager /path/to/public_html/manager
cp -r starter/lazysite/manager /path/to/public_html/lazysite/
Security
Manager access enforcement
Access to /manager and all sub-pages is restricted to authenticated
users whose groups carry the ui capability. Unauthenticated users are
redirected to /login.
Blocked paths
The manager API blocks read and write access to sensitive files:
lazysite/auth/.secretlazysite/forms/.secretlazysite/auth/userslazysite/auth/groups- All
.plfiles
User and group management is handled through the dedicated Users page, not through direct file editing.
Path validation
All file operations validate paths with realpath() to ensure they
resolve within the document root. Path traversal attempts are
rejected.