Documentation

Architecture

How DesertCMS separates static public output, private originals, admin CGI, modules, analytics, comments, ratings, and the shop.

Source: ARCHITECTURE.md

Desert Archive CMS is a hybrid static CMS:

  • httpd serves public generated files directly.
  • /admin is handled by a Perl CGI app through slowcgi.
  • SQLite stores users, sessions, content, media metadata, theme files, revisions, backups, and audit logs.
  • Photo originals stay outside the webroot. Public pages only reference generated derivatives.
  • Production httpd allows 64 MB admin request bodies for full-resolution JPEG uploads; larger public delivery is still avoided by publishing only downscaled derivatives.
  • Editable theme files live under /var/desertcms/themes so application code can remain root-owned.
  • Publishing also refreshes sitemap.xml, robots.txt, tag and collection archive pages, and redirects.httpd.conf.
  • Public analytics are first-party: generated pages post page views to /analytics/collect, which stores path, referrer, timestamp, HMAC-hashed IP/user-agent values, and locally resolved GeoIP fields in SQLite.
  • Public comments are first-party: generated post pages load /comments/thread, post new comments to /comments/create, and check cached-browser reply notices through /comments/notifications.
  • Public ratings are first-party: generated post pages load /ratings/summary and submit one 1-5 star vote per visitor IP hash through /ratings/vote.
  • The image-rights shop is first-party commerce served as a module from the main site at /shop. It uses the main deployment config, database, uploaded media records, and public derivatives rather than contributor-site provisioning.

Deployment Boundary

/usr/local/www/desertcms/          application code
/etc/desertcms.conf                site config
/var/desertcms/desertcms.sqlite    database
/var/desertcms/originals/          private originals
/var/desertcms/backups/            timestamped backups
/var/desertcms/themes/             editable theme files
/var/www/htdocs/desert-archive/    generated public webroot
/var/www/run/desertcms.sock        slowcgi socket visible to httpd
/shop                              shop module route forwarded to the main CGI app

Request Flow

  1. Public requests are handled by httpd as static files.
  2. /admin, /analytics, /comments, /ratings, and /shop requests are forwarded to slowcgi.
  3. Shop module requests for /shop, /shop/checkout, /shop/success, /shop/cancel, and /shop/stripe/webhook are handled by the same CGI app with /etc/desertcms.conf.
  4. slowcgi runs bin/desertcms.cgi as _desertcms.
  5. The CGI app loads /etc/desertcms.conf, connects to SQLite, authenticates the admin where needed, and renders HTML.

Security Intent

  • Dedicated _desertcms user.
  • Writable data isolated under /var/desertcms.
  • Public generated files isolated under /var/www/htdocs/desert-archive.
  • Shop listings reference public display derivatives only; checkout and fulfillment never serve private originals.
  • Admin sessions use random tokens stored as SHA-256 hashes.
  • Anonymous comment names and reply notifications are browser-cached; the database stores only the display name, comment body, and HMAC hashes for the browser token, IP, and user-agent.
  • Passwords use PBKDF2-HMAC-SHA256 with per-password salt.
  • Installer-created temporary CMS admins are forced to set a permanent username and password on first login.
  • The CMS enforces a single active admin account. reset-admin is the controlled recovery path: it disables other admins, revokes existing sessions, and forces first-login setup.
  • CSRF tokens are derived from the session token and app secret.
  • Login attempts are rate limited.
  • Security headers are applied to admin responses.
  • OpenBSD pledge and unveil are used when available.

Editor Direction

The editor stores ordered responsive blocks instead of absolute-positioned boxes. That preserves drag-and-drop editing while keeping mobile rendering reliable.

Publishing Artifacts

Every publish or rebuild refreshes the full public surface:

  • Page and post HTML.
  • /posts/ index.
  • /tags/<slug>/ and /collections/<slug>/ archive pages.
  • /sitemap.xml and /robots.txt.
  • Static redirect fallback pages.
  • redirects.httpd.conf, containing OpenBSD httpd location blocks that can be copied into the public server block.

Analytics GeoIP

Visitor locations are resolved locally from analytics_geoip_ranges inside SQLite. Runtime analytics collection does not call a third-party geolocation API. Load a licensed city database with bin/desertcms-maint.pl geoip-import, then run geoip-backfill once if older analytics rows already exist.

The importer supports simple CSV/TSV rows with either network,country,region,city or start_ip,end_ip,country,region,city. It also supports GeoLite2-style city CSV files by passing both the blocks and locations CSV paths.

Shop Commerce

The shop adds shop_listings, shop_orders, and shop_stripe_events tables. Admin pricing lives at /admin/settings/modules/shop, with personal, commercial, and full-rights prices per uploaded photograph. Public checkout is hosted from /shop; the catalog is /shop, checkout posts to /shop/checkout, and Stripe webhooks post to /shop/stripe/webhook.

A paid full-rights webhook marks the order paid, records the full-rights sale timestamp, disables every rights option for that listing, and removes it from the catalog.

Asset Boundary

Admin and default theme assets are local. Editing and social icons are inline SVG, CSS is served by the app or copied into the public webroot, and no CDN-hosted fonts, scripts, icon kits, or stylesheets are required. tools/check-local-assets.pl audits runtime admin/theme files before deployment.