Server

Two processes run on the box: Caddy, the only public listener, and a small dependency-free Node API bound to localhost that Caddy reverse-proxies to (it also samples the machine's health on a timer). There is no configuration to speak of — every location is fixed in the code, and the only per-install state is one secret. Everything sitehoster puts on disk lives under four roots.

/srv/
  sitehoster/                    the hosted sites, one folder per domain
  sitehoster-logs/               Caddy's access logs
  sitehoster-server-stats/       server-health samples
/etc/
  sitehoster/                    the API token and the TLS cert
  caddy/
    Caddyfile                  the Caddy config
  systemd/
    system/                    the service units
/opt/
  sitehoster/
    <version>/                the server code
The files and directories on the server.

Contents

Every path is fixed in the code; the installer creates all of them and nothing lives outside them.

NameTypeDescription
/srv/sitehoster/directoryThe hosted sites, one folder per domain — what the sync writes and Caddy serves.
/srv/sitehoster-logs/directoryCaddy's combined JSON access log, active plus size-rotated files.
/srv/sitehoster-server-stats/directoryThe server-health samples, one dated file per statistic type.
/etc/sitehoster/directoryThe one secret (the API token) and the self-signed bare-IP certificate.
/etc/caddy/CaddyfilefileThe Caddy config the installer lays down, plus a backup of any earlier one.
/etc/systemd/system/directoryThe service units — the sitehoster API, and Caddy's from its package.
/opt/sitehoster/<version>/directoryThe program itself: every source file the server runs, under a versioned folder.

/srv/sitehoster/

One directory per domain, named exactly for the hostname it serves; this is the tree the sync mirrors up and Caddy serves from (root * /srv/sitehoster/{host}). Adding a folder here and pointing the domain's A record at the box is all it takes to go live — a certificate is issued on the first request. It is owned by the sitehoster service user, and the API only ever writes inside it. The server stores a flat list of domains; local group folders are flattened by the client before upload, so nothing dotless or nested reaches here.

/srv/sitehoster/
  example.com/
    index.html
    styles.css
  www.example.com/              served, because a www. folder exists
  blog.example.com/
  .shtmp-a1b2c3                an in-flight upload, renamed into place atomically
The hosted sites, one folder per domain.

Uploads stream to a hidden .shtmp-* temp file and are renamed atomically, so a half-written file is never served (Caddy hides the pattern). Renaming a local folder to _example.com removes it from here on the next sync — a one-rename "take it offline" switch.

/srv/sitehoster-logs/

Caddy writes one combined JSON access log covering every hosted domain and the proxied API, each line tagged with its host. It rotates by size only (there is no daily rotation), so there is one active file and a trail of size-rotated ones. The API exposes this directory read-only (GET /api/v1/logs) and the client mirrors it down; the directory is group-readable by caddy, which writes it, and by the sitehoster user, which reads it.

/srv/sitehoster-logs/
  access.log                    the active log, one JSON line per request
  access-1784514159284.log      rotated and immutable; the number is its rotation time in ms
  access-1784370489663.log
Caddy's active and rotated access logs.

Rotation is set in the Caddyfile (roll_size 64MiB, roll_keep 50, roll_keep_for 2160h). Rotated files are keyed to the client by mtime, never by name, so filenames never cross the wire.

/srv/sitehoster-server-stats/

The API process samples the machine's own health every 15 seconds and appends one JSON line per statistic type to a dated, per-type day-file. A day-file is frozen the moment the UTC date rolls over — no rename, no lock — and files older than 60 days are pruned. The API serves these read-only (GET /api/v1/stats); the client mirrors each past day once and keeps it, which is the data behind the Server Logs tool.

/srv/sitehoster-server-stats/
  cpu-2026-07-20.log              per-core cumulative CPU times
  memory-2026-07-20.log           total / used / available / free bytes
  disk-2026-07-20.log             usage of the filesystem holding /
  diskio-2026-07-20.log           cumulative bytes read / written
  netio-2026-07-20.log            cumulative bytes in / out
  cpu-2026-07-19.log              yesterday's files, immutable until the retention cutoff
One dated file per statistic, per day.

Rates are never stored: the counter fields are cumulative, so the reader diffs two samples and divides by the true elapsed time — a gap after downtime averages out instead of spiking, and a reboot's counter reset is dropped. Metrics a host can't report (e.g. disk/net IO off Linux) are simply absent.

/etc/sitehoster/

The whole per-install configuration is one secret plus a self-signed certificate. The token is read once at startup and never put in an environment variable (which leaks via /proc); the API refuses to run without it. To rotate it, write a new value and restart the API. The certificate only completes the handshake for IP connections — the token is what authenticates you.

/etc/sitehoster/
  token                          the bearer token, mode 640 root:sitehoster
  tls.crt                        self-signed cert for the bare-IP endpoint
  tls.key                        its private key
The one secret and the bare-IP certificate.

/etc/caddy/Caddyfile

The Caddyfile is fully static — no environment placeholders. It terminates TLS, obtains real certs on demand (gated by a localhost call to the API, so issuance can't be forced for domains you don't host), redirects HTTP → HTTPS and www.X → X, reverse-proxies IP-addressed requests to 127.0.0.1:8787, and serves everything else from /srv/sitehoster/{host}. The installer copies it from the source server/Caddyfile, backing up any file already there once.

/etc/caddy/
  Caddyfile                      our config, installed from server/Caddyfile
  Caddyfile.pre-sitehoster       a one-time backup of any earlier Caddyfile
The Caddy config, and a backup of any earlier one.

/etc/systemd/system/

Two units run at boot. sitehoster ships its own; Caddy's comes from its package (the installer requires Caddy to be installed from a package so this unit exists) and the installer just enables and restarts it. The API unit runs as the unprivileged sitehoster user with NoNewPrivileges, ProtectSystem=full and PrivateTmp, and may write only /srv/sitehoster and /srv/sitehoster-server-stats. It has no EnvironmentFile — the process reads its one secret from /etc/sitehoster/token and hardcodes everything else.

/etc/systemd/system/
  sitehoster-api.service          the localhost API — runs /opt/sitehoster/<version>/server/api.js
  caddy.service                   the public listener, from the Caddy package
The two service units.

/opt/sitehoster/<version>/

The program installs under a versioned directory; the systemd unit points at the exact version, and the two newest are kept so an upgrade can roll back. The tree below is every source file the running server loads — the API entry point and the modules it imports. The installer copies the whole portable shared/ and common/ trees verbatim (they are shared unchanged across the hoster family), but only the files listed here are executed on the server; the rest of common/ is client and tools code that merely rides along. Every module is plain Node with no dependencies.

/opt/sitehoster/
  0.2.0/                          the current version; the previous is kept for rollback
    server/
      api.js                     the API server, and the entry point systemd runs
      Caddyfile                  source of the config installed to /etc/caddy/
    shared/
      manifest.js                Merkle tree and path-safety, shared with the client
      version.js                 the /api/vN protocol version
      stats.js                   one machine-health snapshot
      stats-recorder.js          the loop that writes the stat day-files
    common/
      server/
        logs-stats.js            the read-only /logs and /stats routes
  uninstall.sh                     the full-teardown script, deposited so it runs without the bundle
Every source file the server runs.
NameTypeDescription
server/api.jsfileThe API server and the process systemd starts. A localhost-only HTTP listener carrying the sync routes, the read-only logs/stats routes, the version probe, and Caddy's on-demand-TLS gate. Reads the token once at startup and starts the stats recorder on listen().
server/CaddyfilefileThe production Caddy config, source of the copy at /etc/caddy/Caddyfile.
shared/manifest.jsfileThe Merkle-tree builder and the path-safety helpers, shared with the client so both compute identical hashes and agree on which paths are legal.
shared/version.jsfileThe single API/protocol version that forms the /api/vN path prefix; a mismatch lands on the uniform 404.
shared/stats.jsfileTakes one snapshot of the machine's health — CPU, memory, disk, and cumulative disk- and network-IO counters. Common to every hoster.
shared/stats-recorder.jsfileStarted by the API, it snapshots on the interval, appends a JSON line per type to the dated day-file, and prunes files past the retention window.
common/server/logs-stats.jsfileThe read-only /logs and /stats routes (plus the /stats/now debug snapshot), identical across every hoster.
uninstall.shfileThe full-teardown script the installer deposits at /opt/sitehoster/uninstall.sh so it can be run later without the bundle.

Also copied under the version dir but not run by the API: shared/directives.js (used by the client's automation) and the rest of common/ (net.js, browse/, client/) — the shared client and tools toolkit, carried so one bundle serves every role.