Handlers take (props : dval, request : dval) for COMPONENT and TASK and (request : dval) otherwise; every parameter is optional and keeps its name, and request is bound in every handler body. name : capture type fills an omitted argument from the caller variable of that name. Exported (dval) dval functions carry an implicit request capture. Request builtins capture request; redirect writes request.out. The runtime builds one request value per request, passes request and props handles to handlers, applies request.out once after the top-level handler, and drops the pending-props slot, handler frames, per-handler refresh, and current_request(). Core ABI 38, unit ABI 39. Legacy request type checks and the removed-API migration table are gone from the compiler. Documentation, generated routes, tests, and the project home follow; the home page loses the demo and starter. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
35 KiB
Capy on Bearer setup
This guide describes how to run Capy units on Bearer behind nginx or Apache. Bearer is a FastCGI application server. The web server serves static files and forwards dynamic .capy requests to Bearer.
Deployment shape
A typical deployment has five pieces:
- A checked-out or packaged Bearer runtime tree.
- An application tree that the runtime package does not own.
/etc/bearer/settings.cfg, which Bearer reads at startup.bearer.service, which starts and restarts the runtime.- nginx or Apache as the public HTTP server.
Recommended filesystem layout for a source checkout (replace paths as needed):
/path/to/bearer-root BEARER repository/runtime root (for example `/opt/bearer` or `/Code/bearer.openfu.com/bearer`)
/var/www/html/ public web root served by nginx/Apache
/etc/bearer/settings.cfg runtime configuration
/run/bearer/fastcgi.sock FastCGI socket used by nginx/Apache
/run/bearer/cli.sock local CLI/admin/test socket
/var/cache/bearer/work generated source, wasm modules, caches
/var/lib/bearer/uploads multipart upload scratch space
/var/lib/bearer/sessions session files
A packaged runtime lives under /usr/lib/bearer. The package does not create or populate a public webroot. Install the application separately under /var/www/html or another normal webroot.
Build requirements
On Debian/Ubuntu-like systems, install the distro packages first:
apt update
apt install -y clang build-essential libpcre2-dev libssl-dev mariadb-client libmariadb-dev curl rsync ca-certificates
Bearer requires Wasmtime and the curl binary at run time. http_request() and http_request_async() run curl with an explicit argument vector. The WASI SDK is needed only when you build core.wasm.
-
Wasmtime C API / C++ headers at
/opt/wasmtimeby default.scripts/build_linux.shexpects:/opt/wasmtime/include/wasmtime.hh/opt/wasmtime/include/wasmtime/*.h/opt/wasmtime/lib/libwasmtime.so
-
Pinned WASI SDK at
/opt/wasi-sdkby default.scripts/build_core_wasm.shusesclang++andwasm-ld.
You can use different install locations by setting environment variables before building and in the systemd service environment:
export WASMTIME_HOME=/path/to/wasmtime
export WASI_SDK=/path/to/wasi-sdk
Install one web server:
apt install -y nginx
# or
apt install -y apache2
Build BEARER from your repository root:
repo_root=/path/to/bearer-root
cd "$repo_root"
bash scripts/build_core_wasm.sh
bash scripts/build_linux.sh
Publish your application files into the web root:
mkdir -p /var/www/html
rsync -a site/ /var/www/html/
The main binary is written to:
bin/bearer_fastcgi.linux.bin
Installing Wasmtime and WASI SDK
The BEARER build does not download these dependencies for you. Install Wasmtime through a compatible distro package or a pinned upstream C API archive. Install WASI SDK with BEARER's pinned installer, or unpack the same pinned archive under /opt/wasi-sdk.
Do not use curl | sh installers in production setup scripts. Download archives from the upstream release pages, verify checksums/signatures when available, and record the exact versions in your deployment notes. Avoid installing a release published in the last few days unless you have reviewed it separately.
The expected directories are:
/opt/wasmtime/include/wasmtime.hh
/opt/wasmtime/include/wasmtime/store.h
/opt/wasmtime/lib/libwasmtime.so
/opt/wasi-sdk/bin/clang++
/opt/wasi-sdk/bin/wasm-ld
/opt/wasi-sdk/bin/llvm-objcopy
/opt/wasi-sdk/bin/llvm-nm
/opt/wasi-sdk/bin/llvm-dwarfdump
Install the WASI SDK:
repo_root=/path/to/bearer-root
cd "$repo_root"
scripts/install_wasi_sdk.sh
scripts/install_wasi_sdk.sh --check-only
The current pin is documented in docs/wasi-sdk-toolchain.md. The script verifies the archive SHA256 before installing and updates /opt/wasi-sdk to point at the pinned versioned directory.
For Wasmtime, use a compatible distro package or an upstream C API archive. Example flow using an archive you have already chosen and verified:
mkdir -p /opt /tmp/bearer-deps
cd /tmp/bearer-deps
# Download the Wasmtime C API archive for your architecture from the upstream
# release page, then verify its checksum before unpacking. The archive name
# normally contains "c-api".
sha256sum -c wasmtime-c-api.sha256
mkdir -p /opt/wasmtime
tar -xf wasmtime-*-c-api*.tar.* -C /opt/wasmtime --strip-components=1
After unpacking, verify the tools BEARER needs. Also record the exact Wasmtime and WASI SDK versions used. The native build embeds an rpath for $WASMTIME_HOME/lib, so the service environment should use the same WASMTIME_HOME value used during build.
test -f /opt/wasmtime/include/wasmtime.hh
test -f /opt/wasmtime/lib/libwasmtime.so
/opt/wasi-sdk/bin/clang++ --version
/opt/wasi-sdk/bin/wasm-ld --version
/opt/wasi-sdk/bin/llvm-objcopy --version
/opt/wasi-sdk/bin/llvm-nm --version
/opt/wasi-sdk/bin/llvm-dwarfdump --version
If your paths differ, export the variables for manual builds:
WASMTIME_HOME=/usr/local/wasmtime WASI_SDK=/usr/local/wasi-sdk bash scripts/build_core_wasm.sh
WASMTIME_HOME=/usr/local/wasmtime WASI_SDK=/usr/local/wasi-sdk bash scripts/build_linux.sh
For systemd, add an override:
systemctl edit bearer.service
[Service]
Environment=WASMTIME_HOME=/usr/local/wasmtime
Environment=WASI_SDK=/usr/local/wasi-sdk
Then reload and restart:
systemctl daemon-reload
systemctl restart bearer.service
Runtime configuration
Create /etc/bearer/settings.cfg from etc/bearer/settings.cfg and adjust paths to your checkout root. Replace checkout-specific settings such as WASM_CORE_PATH with <BEARER_REPO_ROOT>/bin/wasm/core.wasm or leave it relative to COMPILER_SYS_PATH as appropriate.
Minimum useful settings:
BIN_DIRECTORY=/var/cache/bearer/work
TMP_UPLOAD_PATH=/var/lib/bearer/uploads
SESSION_PATH=/var/lib/bearer/sessions
SESSION_COOKIE_SECURE=1
FCGI_SOCKET_PATH=/run/bearer/fastcgi.sock
FCGI_SOCKET_MODE=0666
CLI_SOCKET_PATH=/run/bearer/cli.sock
CLI_SOCKET_MODE=0600
SITE_DIRECTORY=/var/www/html
HTTP_DOCUMENT_ROOT=/var/www/html
JIT_COMPILE_ON_REQUEST=1
SHOW_DYNAMIC_COMPILE_ERRORS=1
SERVE_LAST_KNOWN_GOOD=0
PROACTIVE_COMPILE_ENABLED=1
PROACTIVE_COMPILE_JOBS=2
PROACTIVE_COMPILE_CHECK_INTERVAL=60
WASM_BACKEND_VERBOSE=0
WASM_PROFILE_PERFMAP=0
WASM_CORE_PATH=<BEARER_REPO_ROOT>/bin/wasm/core.wasm
WASM_MEMORY_LIMIT_BYTES=536870912
WASM_EPOCH_DEADLINE_TICKS=200
WASM_EPOCH_PERIOD_MS=50
WASM_INVOCATION_TIMEOUT_MS=30000
MYSQL_PERSISTENT_POOL_SIZE=8
KV_CACHE_BYTES=67108864
WORKER_COUNT=4
SESSION_TIME=2592000
HTTP_SOCKET_PATH=/run/bearer/http.sock
HTTP_SOCKET_MODE=0666
HTTP_PORT=
HTTP_BIND_ADDRESS=
WS_BROKER_OUTBOUND_TIMEOUT_SECONDS=30
Important settings:
FCGI_SOCKET_PATHis the Unix socket used for normal.capyrequests. Set it explicitly and keep this value and the web-serverfastcgi_passpath identical. The reference config uses/run/bearer/fastcgi.sock; if you choose/run/bearer.sock, use it in both places.CLI_SOCKET_PATHis a local HTTP-over-Unix socket used byscripts/bearer-cliand test/admin units. Keep it private (CLI_SOCKET_MODE=0600) unless you intentionally delegate admin/test execution to a trusted Unix group (0660).FCGI_SOCKET_MODEandCLI_SOCKET_MODEare octal permission modes applied after socket bind. Prefer tighteningFCGI_SOCKET_MODEto0660when nginx/Apache can share a trusted group with the BEARER worker.SITE_DIRECTORYis the application tree for proactive scans, task targets, and Wasm file access. Use/var/www/htmlwhen the application is outside the runtime tree. Relative paths start at the runtime working directory.HTTP_DOCUMENT_ROOTis the application root for built-in HTTP and WebSocket requests. Set it to the same root as nginx or Apache.- Normal FastCGI requests use the
SCRIPT_FILENAMEsupplied by the web server. One Bearer instance can accept several document roots that share one trust boundary. SITE_DIRECTORYremains process-wide. Independent applications need separate Bearer instances because scans, tasks, WebSockets, sessions, and file access share this setting and runtime state.BIN_DIRECTORYstores runtime state plus ABI-scoped unit generations. Unit source maps, Wasm artifacts, serialized modules, and compile diagnostics live inunits-c<compiler ABI>-w<core ABI>so an upgrade cannot mix generations.TMP_UPLOAD_PATHandSESSION_PATHmust be writable by the runtime.WRITE_PATHSlists further directories, separated by spaces, that pages may write to, for example an upload store outside the site tree. The service sandbox must allow them as well.SESSION_COOKIE_SECURE=1adds theSecureattribute to BEARER-managed session cookies and should be used for HTTPS-only deployments. Leave it0only for local/plain-HTTP development.KV_CACHE_BYTESsizes the shared key-value cache behindkv_get(),kv_set(), andkv_cache(). The parent maps it before forking, so all workers share one cache; the default is 64 MB and the minimum 1 MB. Entries are allocated in power-of-two size classes up to 1 MB.MYSQL_PERSISTENT_POOL_SIZEcaps credential-keyed connections retained by each Wasm worker. The default8is clamped to64; set it to0to restore request-lifetime connections. Cached sessions are reset before reuse.HTTP_SOCKET_PATHis the built-in HTTP and WebSocket listener for nginx proxying and local probes. TCP HTTP is disabled by default. To enable it, set bothHTTP_PORTandHTTP_BIND_ADDRESS. UseHTTP_BIND_ADDRESS=127.0.0.1for a local proxy.WS_BROKER_OUTBOUND_TIMEOUT_SECONDScontrols how long a forwarded WS message can remain queued in the broker before being dropped (default30). Set to0to disable the timeout.SHOW_DYNAMIC_COMPILE_ERRORS=1includes full compiler output and the first diagnostic location in runtime error responses. The setting covers entry units and every dynamic dispatch path. Set it to0to return only the error type, resolved unit path, title, and details. Bearer still returns HTTP 500 and writes one diagnostic summary to the journal.- Nginx must pass Bearer's error body to the client. Add
fastcgi_intercept_errors off;to the FastCGI location. An enabledfastcgi_intercept_errorsdirective replaces the configured runtime error page and the plain-text fallback. SERVE_LAST_KNOWN_GOOD=1lets HTTP GET/HEAD/OPTIONS requests keep using a compatible complete unit artifact while the proactive compiler builds changed source. It defaults to0; CLI and mutation requests always use current code or fail closed. The option requires an enabled proactive compiler with a positive check interval. Failed background builds preserve the prior Wasm, source map, and serialized module until a successful atomic publication replaces them. Missing source or an incompatible compiler/core ABI is never served as last-known-good.PROACTIVE_COMPILE_JOBSselects 1–16 low-priority full-site scanner processes (default2). Each canonical unit path has one scanner owner. When last-known-good serving is enabled, the separate higher-priority demand compiler remains reserved for stale units requested over HTTP, so total background compile concurrency can reach this value plus one.WASM_CORE_PATHmust point at the builtcore.wasmfile.WASM_EPOCH_DEADLINE_TICKSandWASM_EPOCH_PERIOD_MSbound one uninterrupted guest CPU segment.WASM_INVOCATION_TIMEOUT_MSis the separate absolute wall-clock bound for app-owned unit loading, initialization, the selected handler, and all nested component/unit calls. The three values must be positive integers; the ticker period is capped at1000ms and the invocation timeout at86400000ms. Invalid values prevent the Wasm backend from starting. The invocation timeout defaults to30000and is enforced to the epoch ticker's period resolution. Blocking host helpers retain their own shorter limits and are capped to the remaining invocation budget where the underlying operation is cancellable. Cold entry compilation, explicitunit_compile(), dynamic component JIT, transitive#importwork, compiler and registry lock waits, and the configured runtime-error page all consume the same request budget. A timed-out compiler process group is killed without replacing the prior artifact set or persisting a compile-failure backoff. Request compiles require a zero child exit status and complete staged output; generated Capy artifact, exports, source map, Wasm, metadata, cached-module invalidation, and diagnostics are published or rolled back as one guarded generation. Proactive and offline precompile work remain independent of request invocation deadlines. Serialized-module metadata scanning checks the absolute deadline between section headers and before and after every bounded 4 KiB positional read. Cache-miss full-artifact reads use the same checks around 64 KiB positional chunks. A single in-progress regular-file syscall cannot be cooperatively interrupted; later reads and parsing cannot overrun the budget. Initial/final descriptor identity, unique selected metadata sections, and strict 64-bit LEB high-bit validation reject changed or ambiguous artifacts.
After editing settings, restart BEARER:
systemctl restart bearer.service
systemd service
The supplied service runs Bearer as www-data, which is the Debian and Ubuntu web-runtime account. Other distributions use different names. For example, SUSE commonly uses wwwrun. Change User, Group, and socket ownership together when the host uses another account.
The service account must read the application. It must also write the configured cache, session, upload, and task directories. Systemd creates the standard runtime, state, and cache directories for this account.
The Debian package changes the owner of the standard cache and state trees once during migration. For a source checkout, migrate them before you activate the new unit:
systemctl stop bearer.service
chown -R www-data:www-data /var/cache/bearer /var/lib/bearer
scripts/systemd/manage-bearer-service.sh setup
Also change the owner of nonstandard state paths from /etc/bearer/settings.cfg. Do not change the complete application tree unless Bearer must write it.
For source-checkout deployments, install the provided service helper:
repo_root=/path/to/bearer-root
cd "$repo_root"
scripts/systemd/manage-bearer-service.sh setup
That helper installs scripts/systemd/bearer.service from your checked-out tree, rewrites the checked-out repository path into the installed unit and first-time config, creates runtime directories, enables the service, and starts it. Use a custom unit only if you need a nonstandard runtime layout.
Useful commands:
scripts/systemd/manage-bearer-service.sh status
scripts/systemd/manage-bearer-service.sh restart
scripts/systemd/manage-bearer-service.sh logs 200
The server binary accepts only these process modes:
bin/bearer_fastcgi.linux.bin # start the server
bin/bearer_fastcgi.linux.bin --precompile
bin/bearer_fastcgi.linux.bin --help
Help and invalid arguments exit before configuration is loaded or any listener is opened. Invalid or extra arguments return status 2. This ordering prevents a diagnostic invocation from replacing or removing a configured Unix socket.
Managed restart builds and precompiles the complete candidate ABI generation
before it switches the service. Precompile uses two low-priority processes by
default; set PRECOMPILE_JOBS in /etc/bearer/settings.cfg to tune the bounded
1–16 process count for the host. A failed worker, compile, serialization, or
result report aborts the switch and leaves the current service running. The
managed invocation is bounded to 900 seconds by default; set
BEARER_PRECOMPILE_TIMEOUT on manage-bearer-service.sh restart to select another
GNU timeout duration. Timeout exit 124 also aborts before the service switch.
A trusted direct --precompile process may set BEARER_PRECOMPILE_FILES_IN and
BEARER_PRECOMPILE_BIN_DIRECTORY to isolate that invocation's source scan and
artifact registry. Both overrides apply only to precompile mode; normal server
workers continue to use /etc/bearer/settings.cfg. The parallel precompile
regression uses private roots so a running proactive compiler cannot consume
or publish its controlled race fixtures.
Direct --precompile is a listener-free whole-generation gate, not a
single-unit readiness probe. It scans the configured source root, waits for
unit publication locks, and has no internal wall-clock timeout. Automation
must wrap it in a finite external timeout, as the managed restart does, before
starting separately deadline-bounded CLI or HTTP invocations.
Prefer the managed restart when the service runs as an unprivileged user: it precompiles as that same user. Request-time publication still accepts readable artifacts produced by a trusted administrator. When Linux protected-hardlink policy rejects the normal rollback snapshot, BEARER copies the prior artifact under the unit lock; generation markers are replaced atomically instead of requiring write access to the existing inode. Existing readable lock files can be locked without write access, including the shared-PCH lock.
Equivalent manual systemd service for a source checkout (<BEARER_REPO> = checkout root):
[Unit]
Description=BEARER FastCGI Runtime
After=network-online.target mariadb.service memcached.service
Wants=network-online.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=<BEARER_REPO>
RuntimeDirectory=bearer
StateDirectory=bearer
CacheDirectory=bearer
ExecStartPre=/usr/bin/mkdir -p /var/cache/bearer/work /var/lib/bearer/uploads /var/lib/bearer/sessions
ExecStartPre=/usr/bin/rm -f /run/bearer/fastcgi.sock
ExecStart=<BEARER_REPO>/bin/bearer_fastcgi.linux.bin
ExecStopPost=/usr/bin/rm -f /run/bearer/fastcgi.sock
Restart=always
RestartSec=2
TimeoutStopSec=15
KillMode=mixed
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Install it as /etc/systemd/system/bearer.service and run:
systemctl daemon-reload
systemctl enable --now bearer.service
Resource limits
WORKER_COUNT, PROACTIVE_COMPILE_JOBS, and TASK_WORKERS control the main process counts. The defaults are four request workers, two scanner workers, and one task worker. Proactive compilation also starts one priority compiler. WebSocket use adds one broker process.
WASM_MEMORY_LIMIT_BYTES limits the linear memory of one Wasm request workspace. It does not limit the complete service. Use a systemd drop-in for a total memory limit:
systemctl edit bearer.service
[Service]
MemoryHigh=1536M
MemoryMax=2G
Choose limits from measurements on the target host. A package upgrade preserves the drop-in. Restart Bearer after a limit change.
Debian package build
Old experimental packages owned files in their configured webroot. Before an upgrade, deploy those files from the application source or save a backup. The package manager can remove obsolete package-owned files.
To build a Debian package from the repository root:
bash scripts/make_deb.sh 0.1.2
The Debian package contains the Bearer runtime, configuration, service units, and Wasmtime. It does not contain an application or public webroot. Verify the pinned SDK before building:
scripts/install_wasi_sdk.sh --check-only
bash scripts/make_deb.sh 0.1.2
The package includes the Wasmtime shared library, its license files, and the /opt/wasmtime symlink. It omits Wasmtime headers, static libraries, and the WASI SDK. Set BEARER_DEB_BUNDLE_WASMTIME=0 only when the host supplies the tested Wasmtime build.
RPM package build
To build an RPM package from the repository root, install rpmbuild on the packaging host, verify the pinned SDK, then run:
scripts/install_wasi_sdk.sh --check-only
bash scripts/make_rpm.sh 0.1.2
The RPM uses the same boundary. It installs runtime files under /usr/lib/bearer, configuration under /etc/bearer, and service units under /usr/lib/systemd/system. It creates a dedicated bearer account and does not install an application.
The RPM FastCGI socket uses the bearer group and mode 0660. Add the nginx or Apache account to that group before you configure a route. Restart the web server after the group change. Set BEARER_RPM_BUNDLE_WASMTIME=0 only when the host supplies the tested Wasmtime build.
How request routing works
Static files
The web server should serve ordinary static files directly from the public web root, for example /var/www/html.
Examples:
/style.css
/images/logo.png
/js/site.js
These should not touch the BEARER runtime.
Normal .capy page requests
For a request such as:
GET /doc/api/component/
nginx/Apache forwards the request to FCGI_SOCKET_PATH as FastCGI. The web server must provide CGI/FastCGI variables including:
SCRIPT_FILENAME— full filesystem path to the.capyfile.DOCUMENT_ROOT— public web root, normally/var/www/htmlor whatever nginx/Apache uses asroot/DocumentRoot.SCRIPT_NAME— URL path to the script, such as/doc/index.capy.DOCUMENT_URI— normalized URI path without query string.REQUEST_URI— original request URI including query string.- standard request variables such as method, query string, content type, body length, cookies, and headers.
Bearer resolves the Capy unit, compiles it to Wasm when needed, creates a request workspace, and calls:
function RENDER() {
print("response body")
}
The handler reads the shared request value from request. Use print() to write output. Bearer applies request.out once after the top-level handler returns.
Form fields arrive in request.form. Each uploaded file is an entry of request.files with field (the form field name), name (the client file name), size, and temporary_path, a scratch file under TMP_UPLOAD_PATH that lives for the request. A page that keeps an upload moves it with file_rename into a directory under SITE_DIRECTORY or one listed in WRITE_PATHS; when the service sandbox mounts the two roots separately, the move copies the file and removes the original.
Component and unit calls
Capy code can render a component or call an exported function in another unit:
function RENDER() {
component_render("components/card.capy", {title: "News"})
}
These calls stay inside Bearer. They do not create HTTP requests and do not return through nginx or Apache.
WebSocket pages
A Capy unit can provide HTTP rendering and WebSocket message handling:
function RENDER() {
print("ready")
}
function WS() {
ws_send(string(request.body), false)
}
The nginx and Apache examples below split WebSocket upgrades on .capy paths. A file such as chat.capy can expose function WS().
Routing split:
- Plain
GET /chat.capyuses FastCGI, like every page request. - A WebSocket upgrade for
/chat.capyproxies to the Bearer listener atHTTP_SOCKET_PATH. You can also configure a TCP listener.
The listener owns the socket lifecycle. When a message arrives, Bearer invokes function WS() in a request workspace.
CLI requests
function CLI() handlers are not public web endpoints. Bearer invokes them over CLI_SOCKET_PATH:
scripts/bearer-cli /tests/cli.capy action=echo message=hello
curl --unix-socket /run/bearer/cli.sock http://localhost/tests/cli.capy
Use CLI units for local tests, admin commands, and maintenance tools. Do not expose the CLI socket through nginx or Apache.
Custom runtime HTTP servers
Capy can start local custom HTTP listeners with server_start_http(). These listeners are separate from the public nginx or Apache entry point. Bind them locally or protect them with a firewall unless clients must reach them.
nginx configuration
Required modules
A normal nginx build includes the needed FastCGI and proxy modules. Confirm nginx is installed and can load your config:
nginx -t
WebSocket upgrade map
Put this in the nginx http block if using WebSockets:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
Server block
Example site config:
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.capy index.html;
# Serve static files directly. Directory requests prefer index.capy, then index.capy.
location / {
try_files $uri $uri/ =404;
}
# BEARER page requests use FastCGI. If the client asks to upgrade a unit
# request to WebSocket, send that connection to the built-in listener.
location ~ \.(?:uce|capy)$ {
error_page 418 = @bearer_websocket;
if ($http_upgrade = "websocket") {
return 418;
}
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param DOCUMENT_URI $uri;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_pass unix:/run/bearer/fastcgi.sock;
}
location @bearer_websocket {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_pass http://unix:/run/bearer/http.sock:;
}
# Defense in depth if the root is changed later.
location ~ ^/(src|scripts|etc|bin|work|dist|pkg|docs|changelog)/ {
return 404;
}
}
Notes:
fastcgi_passmust matchFCGI_SOCKET_PATH.proxy_passmust matchHTTP_SOCKET_PATH.- The example routes WebSocket upgrades for
.capypaths to the HTTP/WebSocket listener. - The built-in HTTP/WebSocket listener resolves scripts from
HTTP_DOCUMENT_ROOT; do not depend on client-supplied or proxiedScript-Filenameheaders for routing. - Ordinary
.capypage loads continue to use FastCGI. - Keep
rootpointed at/var/www/html, not the runtime repository root. - If your app uses a front controller, replace
location /with atry_filesrule that ends at/index.capy.
Front-controller variant:
location / {
try_files $uri $uri/ /index.capy?$query_string;
}
Reload nginx:
nginx -t
systemctl reload nginx
Apache configuration
Apache can run BEARER through mod_proxy_fcgi for FastCGI and mod_proxy_wstunnel or mod_proxy_http for WebSocket upgrades.
Enable modules
On Debian/Ubuntu:
a2enmod proxy proxy_fcgi proxy_http proxy_wstunnel rewrite headers setenvif
systemctl restart apache2
VirtualHost example
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/html
<Directory /var/www/html>
Require all granted
Options FollowSymLinks
AllowOverride None
DirectoryIndex index.capy index.html
</Directory>
# Do not expose repository internals if DocumentRoot changes later.
<LocationMatch "^/(src|scripts|etc|bin|work|dist|pkg|docs|changelog)/">
Require all denied
</LocationMatch>
RewriteEngine On
# Apache WebSocket proxying uses an explicit loopback TCP listener.
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteCond %{REQUEST_URI} \.capy(?:\?|$) [NC]
RewriteRule ^/(.*)$ ws://127.0.0.1:8080/$1 [P,L]
# Normal .capy page loads go to FastCGI.
<FilesMatch "\.capy$">
SetHandler "proxy:unix:/run/bearer/fastcgi.sock|fcgi://localhost/"
</FilesMatch>
# Optional: make the key CGI variables explicit for BEARER.
ProxyFCGISetEnvIf "true" DOCUMENT_ROOT "/var/www/html"
ProxyFCGISetEnvIf "true" DOCUMENT_URI "%{REQUEST_URI}"
</VirtualHost>
Apache notes:
- For the WebSocket rewrite example, set
HTTP_BIND_ADDRESS=127.0.0.1andHTTP_PORT=8080. The default HTTP Unix socket remains suitable for nginx. SetHandler "proxy:unix:/run/bearer/fastcgi.sock|fcgi://localhost/"must use the same socket path asFCGI_SOCKET_PATH.- The WebSocket rewrite rule must run before the FastCGI handler.
- Plain
.capypage loads should not be proxied as WebSockets unless the client sendsUpgrade: websocket. - Apache's FastCGI environment differs by version and module configuration. If BEARER cannot resolve a page, inspect the request environment and make sure
SCRIPT_FILENAMEpoints to the target file under the web root.
If your Apache version does not populate SCRIPT_FILENAME correctly through SetHandler, use ProxyPassMatch for .capy files instead:
ProxyPassMatch ^/(.*\.capy)$ unix:/run/bearer/fastcgi.sock|fcgi://localhost/var/www/html/$1
Use only one FastCGI mapping style at a time (SetHandler or ProxyPassMatch) to avoid duplicate routing.
Permissions
The web server needs permission to connect to /run/bearer/fastcgi.sock. Common approaches:
- run BEARER and the web server under compatible groups;
- add the web server user (
www-dataon Debian/Ubuntu) to the socket's group; - adjust the service or runtime socket mode if needed.
The runtime creates the FastCGI socket and CLI socket under /run/bearer. The CLI socket should remain local-only and should not be reachable from the public web server.
Writable paths for the runtime:
/var/cache/bearer/work
/var/lib/bearer/uploads
/var/lib/bearer/sessions
/run/bearer
Verification
Check service state:
systemctl status bearer.service
journalctl -u bearer.service -n 100 --no-pager
Check the FastCGI/web-server path:
curl -i http://127.0.0.1/doc/index.capy -H 'Host: example.com'
Check the local CLI path:
repo_root=/path/to/bearer-root
cd "$repo_root"
scripts/bearer-cli /tests/cli.capy action=echo message=hello
scripts/run_cli_tests.sh
Check WebSocket routing with a client against a .capy endpoint that defines function WS() through nginx or Apache. Do not test it directly against HTTP_PORT:
python3 - <<'PY'
import base64, os, socket
host = "example.com"
path = "/chat.capy"
key = base64.b64encode(os.urandom(16)).decode()
request = (
f"GET {path} HTTP/1.1\r\n"
f"Host: {host}\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
f"Sec-WebSocket-Key: {key}\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n"
).encode()
sock = socket.create_connection(("127.0.0.1", 80), timeout=5)
sock.sendall(request)
print(sock.recv(4096).decode("latin1", "replace").split("\r\n", 1)[0])
sock.close()
PY
Password hashing
Use the native password API for application credentials:
String encoded = password_hash(password);
if(encoded == "")
// fail the write; native hashing did not complete
bool valid = password_verify(candidate, encoded);
if(valid && encoded.rfind("$bearer$scrypt$65536$8$1$", 0) != 0)
encoded = password_hash(candidate);
password_hash() returns a self-contained $bearer$scrypt$... encoding with a random 16-byte salt and the bounded scrypt parameters N=65536, r=8, p=1. password_verify() accepts only structurally valid encodings with bounded cost parameters and compares the derived key in constant time. After successful verification, compare the stored parameter prefix with the parameters you require. Rehash the known password when the prefixes differ. Treat an empty hash as an operational failure and never store it. Application-level password length policy, rate limiting, and legacy-format verification remain the application's responsibility.
Operational footguns
- Keep the FastCGI socket path consistent:
FCGI_SOCKET_PATHand the web-serverfastcgi_passmust match exactly. The reference config uses/run/bearer/fastcgi.sock; if you choose/run/bearer.sock, use it in both places. - Keep the public web root separate from the runtime source tree. Replace
/opt/bearerwith your actual checkout path in local examples (for example/opt/beareror/Code/bearer.openfu.com/bearer), and keep public files under/var/www/html. - Set
HTTP_DOCUMENT_ROOTwhen the web root is outside the runtime working directory. The built-in HTTP/WebSocket listener resolves upgrade paths from this setting. - Do not expose
CLI_SOCKET_PATHor an optional HTTP TCP listener as a public entry point. The public path should be nginx or Apache. - Do not trust
Script-Filenamerequest headers from direct HTTP clients. The built-in HTTP listener resolves fromHTTP_DOCUMENT_ROOTand rejects..path segments. - WASI SDK is a build dependency for the WebAssembly core, not just a developer build tool. BEARER compiles units to wasm on demand during requests and during proactive startup scans, so each host must use the pinned SDK version documented in
docs/wasi-sdk-toolchain.md. - After toolchain or compile-script fixes, clear stale failed artifacts under
BIN_DIRECTORY; otherwise a later request may report an old compile failure.
Troubleshooting
502 Bad Gateway
Check:
systemctl status bearer.servicejournalctl -u bearer.service -n 200 --no-pager- socket path in web server config equals
FCGI_SOCKET_PATH - web server user can connect to the Unix socket
SCRIPT_FILENAMEresolves to an existing.capyfile
Raw .capy source is downloaded or displayed
The .capy request did not match the FastCGI rule. Check location/order rules and confirm the public root is /var/www/html or your chosen web-root path.
Static files 404
Confirm the web server root/DocumentRoot is /var/www/html or your chosen web-root path and that location / or Apache directory rules allow static file reads.
WebSocket page renders but upgrade fails
Check:
- the client sends
Upgrade: websocket .capyupgrade traffic reachesHTTP_SOCKET_PATHor the explicitly configured TCP listener- nginx or Apache preserves
UpgradeandConnectionheaders HTTP_DOCUMENT_ROOTmatches the web-server root; if it points at the runtime tree while files live in/var/www/html, the built-in listener will return404 script not found- socket permissions allow the web server to connect, or the firewall allows the explicitly configured local TCP listener
Page compiles fail
Check the compile artifact paths shown in the BEARER error response and service logs. Generated files and compile output live under BIN_DIRECTORY.
Common compile problems:
WASMTIME_HOMEdoes not point at a tree with Wasmtime headers andlibwasmtime.soduring a native build.- The Capy source has a type, syntax, import, or host-call error.
- A previous failed compile left stale
.compile.txt,.wasm-check.txt, or partial.wasmfiles underBIN_DIRECTORY.
Failed compile output is persisted under the unit's ABI-generation path in
BIN_DIRECTORY and may be reused until the source or compiler inputs change.
The managed restart command first runs the new binary's --precompile mode as
the configured service user while the old service remains live. It restarts
systemd only after every scanned unit compiles and serializes successfully.
First fix source/toolchain failures and retry; do not remove the prior generation,
which remains the rollback path. Avoid deleting the whole BIN_DIRECTORY unless
you intentionally want a full rebuild and have accepted losing rollback artifacts.
CLI commands fail
Check:
CLI_SOCKET_PATHin/etc/bearer/settings.cfg/run/bearer/cli.sockexistsscripts/bearer-cli --socket /run/bearer/cli.sock /pingworks- the target Capy unit defines
function CLI()