Private
Public Access
845 lines
36 KiB
Markdown
845 lines
36 KiB
Markdown
# 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()`
|