Documentation

OpenBSD Deployment

Manual OpenBSD deployment notes for packages, filesystem layout, slowcgi, httpd, GeoIP, shop, and redirects.

Source: OPENBSD_DEPLOY.md

For a fresh OpenBSD VPS, use the interactive installer first:

perl install/openbsd-install.pl --dry-run --domain desertarchive.kldhosting.com
doas perl install/openbsd-install.pl

See OPENBSD_INSTALL.md for the full dry-run, server-admin, DNS, pf, httpd, acme-client, local-asset, and filesystem layout walkthrough. The older VULTR_OPENBSD_INSTALL.md remains as a Vultr-specific reference.

These examples assume:

  • Site domain: example.com
  • Shop module path: /shop
  • App code: /usr/local/www/desertcms
  • Config: /etc/desertcms.conf
  • Data: /var/desertcms
  • Public output: /var/www/htdocs/desert-archive
  • Editable themes: /var/desertcms/themes

Packages

pkg_add p5-DBI p5-DBD-SQLite libvips p5-HTTP-Daemon

Firewall

The installer writes a default-deny /etc/pf.conf based on etc/pf.conf.example.

Manual validation:

pfctl -nf /etc/pf.conf
pfctl -f /etc/pf.conf
pfctl -e

Only SSH from the configured admin CIDR and public TCP 80/443 should be passed.

User And Directories

useradd -s /sbin/nologin -d /var/empty _desertcms

install -d -o root -g wheel -m 755 /usr/local/www/desertcms
install -d -o _desertcms -g _desertcms -m 750 /var/desertcms
install -d -o _desertcms -g _desertcms -m 750 /var/desertcms/backups
install -d -o _desertcms -g _desertcms -m 750 /var/desertcms/originals
install -d -o _desertcms -g _desertcms -m 750 /var/desertcms/themes
install -d -o _desertcms -g _desertcms -m 755 /var/www/htdocs/desert-archive
install -d -o www -g www -m 755 /var/www/run

Copy this repository to /usr/local/www/desertcms, then:

cp /usr/local/www/desertcms/etc/desertcms.conf.example /etc/desertcms.conf
chown root:_desertcms /etc/desertcms.conf
chmod 640 /etc/desertcms.conf

Initialize

cd /usr/local/www/desertcms
DESERTCMS_CONFIG=/etc/desertcms.conf perl bin/desertcms-maint.pl init-db
DESERTCMS_CONFIG=/etc/desertcms.conf perl bin/desertcms-maint.pl create-admin setup-admin

init-db also seeds the default editable theme into /var/desertcms/themes/default. When create-admin is run without --password, it prints a temporary password and forces the first CMS login to set the permanent username and password.

The CMS keeps only one active admin account. Use create-admin only for first initialization. For production handoff or recovery, use:

DESERTCMS_CONFIG=/etc/desertcms.conf perl bin/desertcms-maint.pl reset-admin setup-admin

That resets the single active admin, revokes existing sessions, and forces the next login to choose permanent credentials.

Local Asset Audit

Runtime admin and theme assets are local. Before deployment:

perl tools/check-local-assets.pl

The interactive installer runs this automatically before copying the application.

slowcgi

Copy etc/rc.d/desertcms_slowcgi to /etc/rc.d/desertcms_slowcgi, then:

chmod 555 /etc/rc.d/desertcms_slowcgi
rcctl enable desertcms_slowcgi
rcctl start desertcms_slowcgi

The example runs slowcgi with -p / so the CGI script can live outside the httpd chroot while still communicating through /var/www/run/desertcms.sock.

httpd

Merge etc/httpd.conf.example into /etc/httpd.conf, adjust the domain, issue the certificate with acme-client, then:

acme-client -v example.com
httpd -n
rcctl reload httpd

Open:

https://example.com/admin/login

The example also forwards /analytics*, /comments*, and /ratings* to the same Perl CGI app. Public pages remain static files, while analytics collection, post comment threads, and post ratings use those small dynamic endpoints.

For visitor country, region, and city data, import a local GeoIP city database after the CMS is installed. Runtime analytics collection reads the local SQLite GeoIP range table and does not send visitor IPs to an external lookup service.

For a launch-ready free DB-IP City Lite import on a small VPS, use the observed-range mode. It downloads the monthly DB-IP City Lite file, imports only the ranges matching visitor IPs already present in analytics, and backfills those rows:

doas su -m _desertcms -c 'env DESERTCMS_CONFIG=/etc/desertcms.conf perl /usr/local/www/desertcms/bin/desertcms-maint.pl geoip-refresh-dbip-lite --observed-only'

The full DB-IP city file contains millions of ranges and can be too heavy for small VPS deployments. Use the full import only when the server has enough CPU, memory, and disk for the complete table:

doas su -m _desertcms -c 'env DESERTCMS_CONFIG=/etc/desertcms.conf perl /usr/local/www/desertcms/bin/desertcms-maint.pl geoip-refresh-dbip-lite'

DB-IP Lite requires attribution where results are displayed; DesertCMS adds that attribution on the admin dashboard when DB-IP data is active.

For a simple CSV or TSV with network,country,region,city headers:

doas su -m _desertcms -c 'env DESERTCMS_CONFIG=/etc/desertcms.conf perl /usr/local/www/desertcms/bin/desertcms-maint.pl geoip-import /var/desertcms/geoip.tsv'
doas su -m _desertcms -c 'env DESERTCMS_CONFIG=/etc/desertcms.conf perl /usr/local/www/desertcms/bin/desertcms-maint.pl geoip-backfill'

For GeoLite2-style city CSV exports:

doas su -m _desertcms -c 'env DESERTCMS_CONFIG=/etc/desertcms.conf perl /usr/local/www/desertcms/bin/desertcms-maint.pl geoip-import --blocks /var/desertcms/GeoLite2-City-Blocks-IPv4.csv --locations /var/desertcms/GeoLite2-City-Locations-en.csv'
doas su -m _desertcms -c 'env DESERTCMS_CONFIG=/etc/desertcms.conf perl /usr/local/www/desertcms/bin/desertcms-maint.pl geoip-import --append --blocks /var/desertcms/GeoLite2-City-Blocks-IPv6.csv --locations /var/desertcms/GeoLite2-City-Locations-en.csv'
doas su -m _desertcms -c 'env DESERTCMS_CONFIG=/etc/desertcms.conf perl /usr/local/www/desertcms/bin/desertcms-maint.pl geoip-backfill'

Repeat the import periodically when the local GeoIP data is refreshed. The first import replaces the local table; subsequent --append imports add ranges without clearing the table. IPv4 and IPv6 CIDR ranges are supported by the simple importer.

The Shop module forwards /shop* routes to the same CGI app with /etc/desertcms.conf: /shop, /shop/checkout, /shop/success, /shop/cancel, and /shop/stripe/webhook. Static /assets/... files remain served from the main public webroot. Configure Stripe secrets in /etc/desertcms.conf and point the Stripe webhook endpoint at:

https://example.com/shop/stripe/webhook

Keep the HTTPS server block's connection max request body 67108864 line. OpenBSD httpd defaults to 1 MB, so omitting that line causes photo uploads to fail with 413 Payload Too Large. The CMS stores full-resolution originals privately and creates downscaled public display derivatives.

Redirect Rules

The admin Redirects screen writes redirects.httpd.conf into the generated public root. Review that file after publishing and copy its location blocks into the TLS server block in /etc/httpd.conf, then run:

httpd -n
rcctl reload httpd

The CMS also writes static redirect fallback pages so local development and simple static serving still point old URLs at their new targets.