Client

The client is the only sitehoster code that runs on your machine: one command you run on a timer, and the modules behind it. It is stateless — every run rebuilds what it needs from stat(), so nothing stays resident. The source is in three layers: client/, this hoster's own modules; common/, the shared toolkit every hoster's client is built from (the orchestrator, the log browsers, and the plumbing for pulling data and rendering pages); and shared/, the wire contract it holds jointly with the server. This page is the file map — every file, followed by a section listing its exports, or, for the command you run, its switches. The synchronisation protocol and the tools have their own pages for the how and why.

sitehoster-client.js              the one command you run (entry point)
client/                           this hoster's own modules
  sync.js                       mirror the sites to your server
  automate.js                   rewrite every copy from its master
  index-store.js                the change-skipping file index
  browse/
    automation.js               build the Automation Logs browser
common/                           the shared toolkit, common to every hoster
  net.js                        config, TLS and API-version plumbing
  client/
    run.js                      the orchestrator every hoster's client runs
  browse/
    web.js                      build the Web Logs browser
    server.js                   build the Server Logs browser
    pull-logs.js                mirror the access logs down
    pull-stats.js               mirror the server stats down
    shell.js                    the page frame: strip, tabs, status, stylesheet
    ui.js                       the UI kit: cards, tables, charts, pagination
    format.js                   shared formatters (esc, bytes, dates)
    io.js                       small render/IO helpers
shared/                           shared with the server (one wire contract)
  manifest.js                   the Merkle tree + path-safety rules
  directives.js                 the sitehoster: directive scanner
  version.js                    the API/protocol version
The client source, in three layers: this hoster's modules, the shared toolkit, and the wire contract it shares with the server.

Files

FileWhat it does
Entry point
sitehoster-client.jsThe single command you run. Declares the tool set and the sync hook, then hands off to the shared orchestrator.
client/ — this hoster's modules
client/sync.jsCompares the content root to the server by Merkle descent and pushes only the differences.
client/automate.jsReads the on-page master/copy markers and rewrites every copy to match, before anything is pushed.
client/index-store.jsA persisted per-file index that lets a run skip work on files that have not changed.
client/browse/automation.jsReads the site sources and renders the browsable Automation Logs map of every collection and shared block.
common/ — the shared toolkit
common/net.jsReads the config, skips cert verification for a bare-IP endpoint, and discovers the server's API version.
common/client/run.jsThe orchestrator core every hoster's client runs: arg parsing, config, the tool loop, and the tools-site frame.
common/browse/web.jsRenders the static Web Logs browser from the mirrored Caddy access logs.
common/browse/server.jsRenders the static Server Logs browser from the mirrored server statistics, drilling month → minute.
common/browse/pull-logs.jsMirrors the server's access logs down incrementally (rotated files once, the active log by Range).
common/browse/pull-stats.jsMirrors the server's statistics day-files down incrementally, the stats counterpart of pull-logs.
common/browse/shell.jsThe shared page frame: the top strip, tool tabs, per-tool status banner, and stylesheet.
common/browse/ui.jsThe shared UI kit a browser composes: stat cards, tables, chart/table toggles, and pagination.
common/browse/format.jsThe canonical formatters: escaping, numbers, bytes, durations, and month/day labels.
common/browse/io.jsSmall render/IO helpers, chiefly the footer-stripping used by the write-if-changed comparison.
shared/ — the wire contract (shared with the server)
shared/manifest.jsThe Merkle-tree builder and path-safety rules both sides use, so they can never disagree.
shared/directives.jsThe one scanner for sitehoster: directive comments, shared by automation and sync.
shared/version.jsThe API/protocol version that forms the /api/vN path prefix; a mismatch fails fast.

sitehoster-client.js — entry point

The single thing you run, and the only file with no exports. It declares sitehoster's tools — the two universal browsers (Web Logs, Server Logs) plus the project's own Automation Logs — and its content (sync) hook, then hands off to the shared orchestrator in common/client/run.js. Automation is not its own switch: --sync always runs the on-page automation first and then pushes, so un-automated content is never published.

SwitchWhat it does
--config <path>, -cPath to the client config JSON. Defaults to ./config.json.
--syncAutomate every site from its masters, then push the changes to the server.
--web-logsMirror the access logs down and build the Web Logs browser.
--server-logsMirror the server stats down and build the Server Logs browser.
--automation-logsBuild the Automation Logs browser from the site sources.
--allEverything in one process: sync and all three browser tools.

client/sync.js

The sync client. Each run it reads the content root, compares it to the server using recursive Merkle descent over file metadata, and pushes only the differences — nothing stays resident or watches the filesystem. It also mirrors the access logs down (--pull-logs). Importable (the exports below, used by the unified client) and still runnable on its own (node client/sync.js).

ExportWhat it is
once(opts)Run a single sync cycle in-process, so the unified client need not spawn a second Node process. Takes { config, dryRun, verbose, pullLogs }.
buildGroupMap(contentRoot)Scan the content root for group folders and build the map from each grouped member host to its group directory. Empty when no groups exist.
toDiskRel(groupMap, rel)Translate a logical (flattened) site path back to its real path on disk, restoring the group-directory prefix for a grouped host.

client/automate.js

On-page automation. It reads the <!-- sitehoster:… --> comments that mark blocks as a master (the source of truth) or a copy (generated), and rewrites the copies to match — duplication, navigation, records, listings and counts. The whole tree is validated before a byte is written, and a file is only rewritten when its bytes actually change, so the sync is never disturbed by a no-op pass. Also runnable on its own (node client/automate.js --config <path>).

ExportWhat it is
once(opts)Run one in-process automation pass over the content root, rewriting every copy from its master. Takes { config }; the unified client calls this before syncing.

client/index-store.js

A small persisted index at <contentRoot>/.sitehoster-index.json that lets a run skip work on unchanged files. It is a pure optimisation: a missing, out-of-version or corrupt index just means "recompute everything", so it can never cause wrong output, only more or less work. It is a dotfile, so the sync's own scan ignores it.

ExportWhat it is
load(contentRoot)Load the index, or an empty one if it is missing, a different version, or unreadable.
save(contentRoot, index)Write the index back to disk under the content root.
indexPath(contentRoot)The absolute path to the index file for a given content root.
stampOf(st)A file's change-stamp from a stat(): raw byte size plus whole-second mtime.
sameStamp(a, b)Whether two change-stamps are equal — i.e. the file is unchanged.
INDEX_NAMEThe dotfile name of the index, .sitehoster-index.json.
VERSIONThe index schema version; a mismatch forces a clean full rebuild.

client/browse/automation.js

The Automation Logs browser generator, and this hoster's own domain tool. Where the log browsers read the access logs, this one reads the site source — every HTML file under the sibling site folders — pulls out all of the automation markers, and renders a browsable map of the records, listings, counts, duplication and navigation behind each site. It composes the shared shell, UI kit and formatters, so it feels like one tool with the log browsers.

ExportWhat it is
build(options)Scan the site sources and render the automation browser. The client passes explicit { sitesParent, outDir }; run standalone it falls back to __dirname.

common/net.js

The shared API-client plumbing every hoster's downloaders and sync sit on: read the config, skip cert verification for a bare-IP (self-signed) endpoint, and discover the server's API version so request paths follow it. Meant to be copied across the hosters unchanged.

ExportWhat it is
loadConfig(path)Read and validate the client config JSON into the shape the tools expect.
isIpHttps(apiBase)Whether the endpoint is HTTPS to a bare IP literal (or [ipv6]) — hence self-signed.
disableTlsVerification()Quiet the one-time Node warning and disable TLS verification process-wide (only for an IP endpoint).
resolvePrefix(...)Discover the server's API version and build the /api/vN path prefix the requests use.

common/client/run.js

The hoster-tools orchestrator core — the shared engine every hoster's client runs. A project calls run({ tools, sync, brand }); this owns arg parsing, config and TLS, the isolated phase loop, and the whole tools-site frame (overview cards, per-tool pages, clash-safe "not run" status, persisted last-run state). The two universal tools come ready-made from webTool()/serverTool(), so a sibling hoster's entry is a short declaration.

ExportWhat it is
run(opts)The entry a project calls with { tools, sync, brand }: parse args, run the sync hook and every enabled tool, and write the tools site.
webTool()The ready-made Web Logs tool descriptor (pull the access logs, then build the browser).
serverTool()The ready-made Server Logs tool descriptor (pull the stats, then build the browser).

common/browse/web.js

The Web Logs browser generator. It reads the Caddy JSON access logs mirrored down by pull-logs.js and renders a set of static, self-contained HTML pages so the traffic can be browsed offline. Composes the shared shell, UI kit and formatters; also runnable standalone.

ExportWhat it is
build(options)Render the Web Logs browser. The client passes { logsDir, outDir }; run standalone it reads the log dir beside the script.

common/browse/server.js

The Server Logs browser generator — the reader half of the shared stats facility. It reads the per-type dated day-files mirrored down by pull-stats.js and writes a drill-down site (all time → month → day → hour → minute), each page charting every metric with a chart/table toggle.

ExportWhat it is
build(options)Render the Server Logs browser. The client passes { dataDir, outDir, serverIp }; run standalone it reads the data dir beside the script.

common/browse/pull-logs.js

Mirror the server's Caddy access logs into a data dir — the log counterpart of pull-stats.js. Rotated files are immutable (fetched once, whole); the active log grows, so only its new tail is appended via a Range request; a newly-appeared rotated file means the active log restarted, so it resets and re-fetches. TLS is expected to be already configured by the caller.

ExportWhat it is
pull(opts)Mirror the access logs down incrementally. Takes { logsDir, apiBase, apiToken } and reports how many lines were mirrored.

common/browse/pull-stats.js

Mirror the server's statistics day-files into a data dir — the stats counterpart of pull-logs.js. The server samples CPU, memory, disk and IO into per-type dated files; a frozen past day is fetched once and today's growing file has only its tail appended by Range. TLS is expected to be already configured by the caller.

ExportWhat it is
pull(opts)Mirror the stats day-files down incrementally. Takes { dataDir, apiBase, apiToken } and reports what was fetched and appended.

common/browse/shell.js

The shared shell that owns the page frame for the tools site: the fixed top strip (brand + Overview + a tab per tool + the IP), the per-tool status banner, the stylesheet, and small formatters. The same strip goes on every page; the tab set and brand are configured per project by run.js at startup, so this file carries no project identity and copies across the hosters unchanged.

ExportWhat it is
layout(opts)Render a full framed page (top strip + body) for the Overview and any not-run tool's status page.
toolStrip(...)The fixed top strip a tool browser injects at the top of its own pages, so the tabs are identical everywhere.
statusBanner(tool)The per-tool banner shown when a tool has not been run.
overviewCard(tool, state)One tool's card on the Overview, reflecting its last-run state.
setTools(tools), setBrand(brand)Configure the tab set and brand for this project (called by run.js); TOOLS, brandName() expose them.
esc, fmtNum, fmtBytes, stripFooterThe formatters and footer-stripper re-exported for convenience, plus STYLESHEET and TOOLSTRIP_CSS.

common/browse/ui.js

The shared UI kit for the tools browsers — the reusable builders a domain tool composes instead of hand-rolling markup. The page frame lives in shell.js and the formatters in format.js; specialised chart bodies stay per-browser, bound to each tool's data.

ExportWhat it is
panel(...), table(...)A titled panel, and a table with a built-in Show-all toggle.
statCards(items)A row of stat blocks (name + value, or a custom cell).
navButtons(prev, next, up)Prev / Next (with an optional Up) pagination, used on every dated page.
viewToggle(...)A chart ⇄ table switch for a metric.
SCRIPT, CSSThe shared client JS (table toggle + chart switch) and the common visual furniture.

common/browse/format.js

The canonical formatters shared by the tools browsers — pure functions with no state, meant to be copied across the hoster projects unchanged.

ExportWhat it is
esc(s)HTML-escape a value for safe interpolation.
fmtNum(n)A grouped decimal number (thousands separators).
fmtBytes(b)A human byte size (B, KB, MB, …).
fmtDuration(ms)A compact duration (ms, s, or m s).
monthLabel, monthLabelShort, dayLabel, MONTH_NAMESMonth/day label helpers and the month-name table used by the dated pages.

common/browse/io.js

Small shared render/IO helpers for the tools browsers. The footer carries the page's generation time, which changes every run; stripFooter removes the whole footer before the write-if-changed comparison so a new timestamp alone never forces a rewrite.

ExportWhat it is
stripFooter(html)Remove the footer block before comparing a freshly rendered page to the one on disk.
rmrf(p)Recursively remove a path (for a clean full rebuild).
GEN_DURATION_TOKENThe placeholder a browser writes for the generation time and patches with the real value last.

shared/manifest.js

The Merkle-tree and path-safety helpers shared by the server API and the sync client. A file's hash is derived from its metadata (size + whole-second mtime), never its contents, so building the tree reads no file data; equal directory hashes mean the same shape and per-file metadata throughout, which is what lets the client compare one root hash and descend only into subtrees that differ.

ExportWhat it is
buildTree(root, rel)Build the Merkle tree node for a directory from stat() alone.
createMerkleTree(...)The underlying tree/hash constructor buildTree is built on.
isAllowedRelPath(p)Whether a relative path is safe: non-dot, URL-safe segments, no traversal or absolute paths.
normalizeRelPath(p)Normalise a relative path to the canonical form both sides agree on.
resolveInRoot(root, rel)Resolve a validated relative path to an absolute one confined within the content root.

shared/directives.js

The shared sitehoster directive helpers: one comment scanner and one "is this a directive" gate, used by the automation (to parse the directive comments) and by the sync client (to strip them from the copies it uploads), so the two sides can never disagree about what a directive is.

ExportWhat it is
scanComments(text)Every HTML comment in the text, with byte offsets.
stripDirectives(html)Remove every sitehoster: directive comment from rendered HTML, keeping everything else.
DIRECTIVE_REThe single regex that recognises a directive comment's inner text.
parseHead(...), parseFences(...), dedent(...)Parse a marker's opening attributes and fenced template bodies, and dedent them.

shared/version.js

The API/protocol version — the /api/vN path prefix the client and server both use, so a mismatch lands on the uniform 404 (fail-fast) instead of silently misparsing. It is bumped whenever any part of the wire contract changes, and is distinct from the code release version in the VERSION file.

ExportWhat it is
API_VERSIONThe current wire-contract version number (an integer), today 1.