CyberPanel: supported from v0.1.2. CyberPanel replaces OpenLiteSpeed with its own core (2.5.x) whose
lsi_api_tcarries three extra members; hatch detects that layout at load time. v0.1.1 and older crash the worker on the first request there, so upgrade before installing on a CyberPanel server.
Webuzo: supported. Webuzo ships a stock OpenLiteSpeed core, so the module needs no special build; the installer adds a guard that restores the
module hatchblock when Webuzo rewriteshttpd_config.conf, and a watcher that reloads OpenLiteSpeed within seconds of a.htaccesschange.
Apache .htaccess compatibility for OpenLiteSpeed, as an open source module written in Rust.
OpenLiteSpeed reads RewriteRule from .htaccess on its own, but ignores everything else (Header, Redirect, Require, <FilesMatch> and friends). hatch loads into OpenLiteSpeed and applies those directives per request, the way Apache does. Edits take effect on the next request; no restart.
RequestHeader cannot be implemented: LSIAPI can read a request header but has no call that changes one. The line is logged and skipped.
Next (v0.2): Basic Auth (AuthType, AuthUserFile, Require valid-user), DirectoryIndex.
Unsupported directives are logged once per file change and skipped; they never break the site.
curl -fsSL https://raw.githubusercontent.com/prabowosd/hatch/main/install.sh | sudo bashThe installer checks the architecture and glibc (2.34+: RHEL/Alma/Rocky 9 and 10, Ubuntu 22.04+), verifies the release checksum, backs up httpd_config.conf and any previous module, adds
module hatch {
ls_enabled 1
}
and does a graceful restart. If OpenLiteSpeed does not come back with hatch loaded, it restores the previous module and config on its own.
sudo bash install.sh --file ./libhatch.so # install a local build
sudo bash install.sh --version v0.1.0 # pin a release
sudo bash install.sh --no-guard # skip the Webuzo config guard
sudo bash install.sh --status
sudo bash install.sh --uninstallCyberPanel ships its own OpenLiteSpeed core. hatch recognises it by the cyberpanel_lsi_api_abi_v1 symbol the core exports and reads the table with that layout. On a core with an unknown marker it logs one line and stays idle instead of guessing. The bundled cyberpanel_ols module needs a paid license and stays inert without one; hatch runs next to it.
Webuzo 4.x runs a stock OpenLiteSpeed core, so hatch loads on it unchanged: there is no ABI marker to read and no special build. Its vhosts use configFile and autoLoadHtaccess 1, with document roots under /home/<user>/<domain>/public. hatch asks the server for the document root, so the .htaccess beside the site is found either way.
What differs is who owns the config. Webuzo writes httpd_config.conf and every vhost under conf/webuzo_vh/ from its own state — on a certificate renewal, a domain or account change, and sometimes from its own service check — so a module hatch block added by hand can disappear. When the installer sees Webuzo (/usr/local/webuzo or conf/webuzo_vh/), it also installs /usr/local/bin/hatch-guard with a one-minute entry in /etc/cron.d/hatch-guard. The guard puts the block back and restarts OpenLiteSpeed only when Webuzo has removed it; a config that still has the block is left alone.
RewriteRule is the one family OpenLiteSpeed keeps for itself: it parses .htaccess at config load and caches the rules, so a rewrite edit waits for a reload. Webuzo compensates with a two-minute cron that reloads when a .htaccess is newer than cgid; CyberPanel does the same every three minutes. On Webuzo the installer also adds hatch-reload, a systemd service that watches the document roots from conf/webuzo_vh/ with inotifywait, ignores every file except .htaccess, debounces two seconds, and reloads at most once every ten seconds. Rewrite edits apply within seconds instead of minutes, and the service re-reads the vhost list every five minutes so new sites are covered. It needs inotify-tools.
sudo bash install.sh --no-guard # skip the config guard
sudo bash install.sh --no-watch # skip the .htaccess watcher
sudo bash install.sh --status # shows whether Webuzo, the guard and the watcher are presentThe directives hatch handles itself (Header, Redirect, Require, FilesMatch, Expires and the rest) never wait for a reload: they are read from .htaccess on each request, so an edit applies to the next request.
php_value and php_flag are logged and skipped on Webuzo's stock core, the same as on any OpenLiteSpeed build without CyberPanel's API. OpenLiteSpeed has no module API for per-request PHP configuration; phpIniOverride in a vhost or context block is the static alternative, and Webuzo keeps per-domain files for it under /var/webuzo-data/openlitespeed/custom/.
install.sh edits an OpenLiteSpeed installed on the host. For a container, mount the release .so and a config that enables it instead:
services:
openlitespeed:
image: litespeedtech/openlitespeed:latest
volumes:
- ./hatch-x86_64-linux-gnu.so:/usr/local/lsws/modules/hatch.so:ro
- ./httpd_config.conf:/usr/local/lsws/conf/httpd_config.conf:roAdd this block to httpd_config.conf, then restart the container:
module hatch {
ls_enabled 1
}
Use the x86_64 or aarch64 asset that matches the container. The image must use glibc (the official OpenLiteSpeed images do); a musl/Alpine image cannot load the module.
- Hooks
URI_MAP(redirects and access control) andSEND_RESP_HEADER(headers and expiry). - For each request it walks from the docroot to the file's folder and merges every
.htaccesson the way: headers accumulate from parent to child, the deepest access rules win, the deepest matching redirect wins. - Parsed files are cached per worker and re-read only when their mtime or size changes. Files over 256 KB are ignored.
- Regexes use the
regexcrate, which runs in linear time, so a hostile pattern cannot stall a worker. - Header names, values and redirect targets are validated; anything that could inject a header or a
javascript:redirect is dropped. - The server's function table is read once per worker. CyberPanel's core has a longer
lsi_api_t, so hatch picks the layout from the core's ABI marker first and falls back to scanning the server binary. - Every hook runs inside
catch_unwindand fails open: a bug in hatch skips the.htaccessrules for that request instead of taking the server down.
The module links against OpenLiteSpeed's LSIAPI (ols-api/, GPLv3, from OpenLiteSpeed 1.8.3). Builds run in a container so the result matches the target glibc:
podman build -t hatch-build -f build/Containerfile build
podman run --rm -v "$PWD":/src:Z hatch-build cargo build --release
# target/release/libhatch.sopodman run --rm -v "$PWD":/src:Z hatch-build cargo test # unit tests
tests/integration.sh target/release/libhatch.so # real OpenLiteSpeed in a container
HATCH_CYBERPANEL=1 tests/integration.sh libhatch.so # CyberPanel's OpenLiteSpeed core (x86_64)
HATCH_LOCAL=1 tests/integration.sh libhatch.so # the OpenLiteSpeed installed on this hostCI runs all of them on x86_64 and aarch64: OpenLiteSpeed 1.8 (official image), 1.9 on AlmaLinux 10 (LiteSpeed RPM), and CyberPanel's core built from build/CyberPanelfile.
When the module loads but a rule does not apply, create /usr/local/lsws/conf/hatch.debug and restart OpenLiteSpeed. hatch then logs, per request, what the server handed it: the URI, the doc root, the resolved file path and how many .htaccess files apply.
sudo touch /usr/local/lsws/conf/hatch.debug
sudo /usr/local/lsws/bin/lswsctrl restart
tail -f /usr/local/lsws/logs/error.log | grep '\[hatch\]'The check runs once per worker thread, so the restart is required after creating or removing the file. Entries are written at WARN level to the vhost error log, so an errorlog block with logLevel ERROR will suppress them. Remove the file and restart when done, since an active trace writes several lines per request.
GPL-3.0-or-later, the same as OpenLiteSpeed. hatch is an independent project and is not affiliated with LiteSpeed Technologies or CyberPanel.
