Files

845 lines
36 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
## Editor-only installation
For highlighting, language-server features, source checks and formatting, use the [offline editor tools](editor-tools.md). The VSIX includes native compilers for Linux x86-64 and Apple Silicon macOS; the Linux installer also provides a standalone `capyc`. Editor tooling does not require any runtime services or the server dependencies below.
## Deployment shape
A typical deployment has five pieces:
1. A checked-out or packaged Bearer runtime tree.
2. An application tree that the runtime package does not own.
3. `/etc/bearer/settings.cfg`, which Bearer reads at startup.
4. `bearer.service`, which starts and restarts the runtime.
5. nginx or Apache as the public HTTP server.
Recommended filesystem layout for a source checkout (replace paths as needed):
```text
/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:
```bash
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/wasmtime` by default. `scripts/build_linux.sh` expects:
- `/opt/wasmtime/include/wasmtime.hh`
- `/opt/wasmtime/include/wasmtime/*.h`
- `/opt/wasmtime/lib/libwasmtime.so`
- **Pinned WASI SDK** at `/opt/wasi-sdk` by default. `scripts/build_core_wasm.sh` uses `clang++` and `wasm-ld`.
You can use different install locations by setting environment variables before building and in the systemd service environment:
```bash
export WASMTIME_HOME=/path/to/wasmtime
export WASI_SDK=/path/to/wasi-sdk
```
Install one web server:
```bash
apt install -y nginx
# or
apt install -y apache2
```
Build BEARER from your repository root:
```bash
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:
```bash
mkdir -p /var/www/html
rsync -a site/ /var/www/html/
```
The main binary is written to:
```text
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:
```text
/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:
```bash
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:
```bash
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.
```bash
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:
```bash
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:
```bash
systemctl edit bearer.service
```
```ini
[Service]
Environment=WASMTIME_HOME=/usr/local/wasmtime
Environment=WASI_SDK=/usr/local/wasi-sdk
```
Then reload and restart:
```bash
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:
```ini
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_PATH` is the Unix socket used for normal `.capy` requests. Set it explicitly and keep this value and the web-server `fastcgi_pass` path identical. The reference config uses `/run/bearer/fastcgi.sock`; if you choose `/run/bearer.sock`, use it in both places.
- `CLI_SOCKET_PATH` is a local HTTP-over-Unix socket used by `scripts/bearer-cli` and 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_MODE` and `CLI_SOCKET_MODE` are octal permission modes applied after socket bind. Prefer tightening `FCGI_SOCKET_MODE` to `0660` when nginx/Apache can share a trusted group with the BEARER worker.
- `SITE_DIRECTORY` is the application tree for proactive scans, task targets, and Wasm file access. Use `/var/www/html` when the application is outside the runtime tree. Relative paths start at the runtime working directory.
- `HTTP_DOCUMENT_ROOT` is 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_FILENAME` supplied by the web server. One Bearer instance can accept several document roots that share one trust boundary.
- `SITE_DIRECTORY` remains process-wide. Independent applications need separate Bearer instances because scans, tasks, WebSockets, sessions, and file access share this setting and runtime state.
- `BIN_DIRECTORY` stores runtime state plus ABI-scoped unit generations. Unit
source maps, Wasm artifacts, serialized modules, and compile diagnostics live in
`units-c<compiler ABI>-w<core ABI>` so an upgrade cannot mix generations.
- `TMP_UPLOAD_PATH` and `SESSION_PATH` must be writable by the runtime.
- `WRITE_PATHS` lists 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=1` adds the `Secure` attribute to BEARER-managed session cookies and should be used for HTTPS-only deployments. Leave it `0` only for local/plain-HTTP development.
- `KV_CACHE_BYTES` sizes the shared key-value cache behind `kv_get()`, `kv_set()`, and `kv_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_SIZE` caps credential-keyed connections retained by each Wasm worker. The default `8` is clamped to `64`; set it to `0` to restore request-lifetime connections. Cached sessions are reset before reuse.
- `HTTP_SOCKET_PATH` is the built-in HTTP and WebSocket listener for nginx proxying and local probes. TCP HTTP is disabled by default. To enable it, set both `HTTP_PORT` and `HTTP_BIND_ADDRESS`. Use `HTTP_BIND_ADDRESS=127.0.0.1` for a local proxy.
- `WS_BROKER_OUTBOUND_TIMEOUT_SECONDS` controls how long a forwarded WS message can remain queued in the broker before being dropped (default `30`). Set to `0` to disable the timeout.
- `SHOW_DYNAMIC_COMPILE_ERRORS=1` includes full compiler output and the first diagnostic location in runtime error responses. The setting covers entry units and every dynamic dispatch path. Set it to `0` to 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 enabled `fastcgi_intercept_errors` directive replaces the configured runtime error page and the plain-text fallback.
- `SERVE_LAST_KNOWN_GOOD=1` lets HTTP GET/HEAD/OPTIONS requests keep using a compatible complete unit artifact while the proactive compiler builds changed source. It defaults to `0`; 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_JOBS` selects 1–16 low-priority full-site scanner processes (default `2`). 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_PATH` must point at the built `core.wasm` file.
- `WASM_EPOCH_DEADLINE_TICKS` and `WASM_EPOCH_PERIOD_MS` bound one
uninterrupted guest CPU segment. `WASM_INVOCATION_TIMEOUT_MS` is 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 at
`1000` ms and the invocation timeout at `86400000` ms. Invalid values prevent
the Wasm backend from starting. The invocation timeout defaults to `30000` and 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,
explicit `unit_compile()`, dynamic component JIT, transitive `#import` work,
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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:
```bash
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):
```ini
[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:
```bash
systemctl daemon-reload
systemctl enable --now bearer.service
```
### Resource limits
`WORKER_COUNT` and `PROACTIVE_COMPILE_JOBS` control the main process counts. The defaults are four request workers and two scanner workers. Tasks and shell callbacks run in detached processes started on demand, and services in one supervised worker each. 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:
```bash
systemctl edit bearer.service
```
```ini
[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
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:
```bash
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:
```bash
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:
```text
/style.css
/images/logo.png
/js/site.js
```
These should not touch the BEARER runtime.
### Normal `.capy` page requests
For a request such as:
```text
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 `.capy` file.
- `DOCUMENT_ROOT` — public web root, normally `/var/www/html` or whatever nginx/Apache uses as `root`/`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:
```capy
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:
```capy
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:
```capy
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.capy` uses FastCGI, like every page request.
- A WebSocket upgrade for `/chat.capy` proxies to the Bearer listener at `HTTP_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`:
```bash
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:
```bash
nginx -t
```
### WebSocket upgrade map
Put this in the nginx `http` block if using WebSockets:
```nginx
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
```
### Server block
Example site config:
```nginx
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_pass` must match `FCGI_SOCKET_PATH`.
- `proxy_pass` must match `HTTP_SOCKET_PATH`.
- The example routes WebSocket upgrades for `.capy` paths to the HTTP/WebSocket listener.
- The built-in HTTP/WebSocket listener resolves scripts from `HTTP_DOCUMENT_ROOT`; do not depend on client-supplied or proxied `Script-Filename` headers for routing.
- Ordinary `.capy` page loads continue to use FastCGI.
- Keep `root` pointed at `/var/www/html`, not the runtime repository root.
- If your app uses a front controller, replace `location /` with a `try_files` rule that ends at `/index.capy`.
Front-controller variant:
```nginx
location / {
try_files $uri $uri/ /index.capy?$query_string;
}
```
Reload nginx:
```bash
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:
```bash
a2enmod proxy proxy_fcgi proxy_http proxy_wstunnel rewrite headers setenvif
systemctl restart apache2
```
### VirtualHost example
```apache
<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.1` and `HTTP_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 as `FCGI_SOCKET_PATH`.
- The WebSocket rewrite rule must run before the FastCGI handler.
- Plain `.capy` page loads should not be proxied as WebSockets unless the client sends `Upgrade: 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_FILENAME` points 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:
```apache
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-data` on 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:
```text
/var/cache/bearer/work
/var/lib/bearer/uploads
/var/lib/bearer/sessions
/run/bearer
```
## Verification
Check service state:
```bash
systemctl status bearer.service
journalctl -u bearer.service -n 100 --no-pager
```
Check the FastCGI/web-server path:
```bash
curl -i http://127.0.0.1/doc/index.capy -H 'Host: example.com'
```
Check the local CLI path:
```bash
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`:
```bash
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:
```cpp
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_PATH` and the web-server `fastcgi_pass` must 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/bearer` with your actual checkout path in local examples (for example `/opt/bearer` or `/Code/bearer.openfu.com/bearer`), and keep public files under `/var/www/html`.
- Set `HTTP_DOCUMENT_ROOT` when 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_PATH` or an optional HTTP TCP listener as a public entry point. The public path should be nginx or Apache.
- Do not trust `Script-Filename` request headers from direct HTTP clients. The built-in HTTP listener resolves from `HTTP_DOCUMENT_ROOT` and 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.service`
- `journalctl -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_FILENAME` resolves to an existing `.capy` file
### 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`
- `.capy` upgrade traffic reaches `HTTP_SOCKET_PATH` or the explicitly configured TCP listener
- nginx or Apache preserves `Upgrade` and `Connection` headers
- `HTTP_DOCUMENT_ROOT` matches the web-server root; if it points at the runtime tree while files live in `/var/www/html`, the built-in listener will return `404 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_HOME` does not point at a tree with Wasmtime headers and `libwasmtime.so` during 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 `.wasm` files under `BIN_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_PATH` in `/etc/bearer/settings.cfg`
- `/run/bearer/cli.sock` exists
- `scripts/bearer-cli --socket /run/bearer/cli.sock /ping` works
- the target Capy unit defines `function CLI()`