# UCE Runtime Setup

This guide describes how to run UCE behind nginx or Apache. UCE is a FastCGI application server for `.uce` units; the web server should serve static files directly and forward dynamic `.uce` requests to the UCE runtime.

## Deployment shape

A typical deployment has four pieces:

1. A checked-out or packaged UCE runtime tree.
2. `/etc/uce/settings.cfg`, read by the UCE runtime at startup.
3. `uce.service`, a systemd service that builds/starts/restarts the runtime.
4. nginx or Apache as the public HTTP server.

Recommended filesystem layout for a source checkout:

```text
/opt/uce/                         UCE repository/runtime root
/var/www/html/                    public web root served by nginx/Apache
/etc/uce/settings.cfg             runtime configuration
/run/uce/fastcgi.sock             FastCGI socket used by nginx/Apache
/run/uce/cli.sock                 local CLI/admin/test socket
/var/cache/uce/work               generated source, wasm modules, caches
/var/lib/uce/uploads              multipart upload scratch space
/var/lib/uce/sessions             session files
```

For packaged installs, the runtime may live under `/usr/lib/uce` instead of `/opt/uce`. Keep the public web root at `/var/www/html` or another normal web-root path, not under the runtime source tree.

## Build requirements

On Debian/Ubuntu-like systems, install the distro packages first:

```bash
apt update
apt install -y clang build-essential libpcre2-dev mariadb-client libmariadb-dev curl rsync ca-certificates
```

UCE also requires two toolchains that are not vendored in this repository:

- **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`
- **WASI SDK** at `/opt/wasi-sdk` by default. `scripts/build_core_wasm.sh` and `scripts/compile_wasm_unit` expect:
  - `/opt/wasi-sdk/bin/clang++`
  - `/opt/wasi-sdk/bin/wasm-ld`
  - `/opt/wasi-sdk/bin/llvm-objcopy`

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 UCE from the repository root:

```bash
cd /opt/uce
bash scripts/build_core_wasm.sh
bash scripts/build_linux.sh
```

Publish the starter site or 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/uce_fastcgi.linux.bin
```

### Installing Wasmtime and WASI SDK

The UCE build does not download these dependencies for you. Install them through your distro if it provides compatible development packages, or unpack pinned release archives under `/opt/wasmtime` and `/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 keep the exact versions recorded in your deployment notes.

The expected directory shape is:

```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
```

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 uce.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 uce.service
```

## Runtime configuration

Create `/etc/uce/settings.cfg` from `etc/uce/settings.cfg` and adjust paths if your runtime is not under `/opt/uce`.

Minimum useful settings:

```ini
BIN_DIRECTORY=/var/cache/uce/work
TMP_UPLOAD_PATH=/var/lib/uce/uploads
SESSION_PATH=/var/lib/uce/sessions

FCGI_SOCKET_PATH=/run/uce/fastcgi.sock
CLI_SOCKET_PATH=/run/uce/cli.sock

SITE_DIRECTORY=/var/www/html
JIT_COMPILE_ON_REQUEST=1
PROACTIVE_COMPILE_ENABLED=1
PROACTIVE_COMPILE_CHECK_INTERVAL=60

WASM_COMPILE_SCRIPT=scripts/compile_wasm_unit
WASM_BACKEND_VERBOSE=0
WASM_CORE_PATH=/opt/uce/bin/wasm/core.wasm
WASM_MEMORY_LIMIT_BYTES=536870912
WASM_EPOCH_DEADLINE_TICKS=200
WASM_EPOCH_PERIOD_MS=50

WORKER_COUNT=4
MAX_MEMORY=16777216
SESSION_TIME=2592000

HTTP_PORT=8080
```

Important settings:

- `FCGI_SOCKET_PATH` is the Unix socket used for normal `.uce` requests.
- `CLI_SOCKET_PATH` is a local HTTP-over-Unix socket used by `scripts/uce-cli` and test/admin units.
- `SITE_DIRECTORY` is the public site tree to scan for `.uce` files. Use `/var/www/html` when the web root is outside the runtime tree; relative paths are resolved from the runtime working directory.
- `BIN_DIRECTORY` stores generated C++, wasm artifacts, compile output, and runtime caches.
- `TMP_UPLOAD_PATH` and `SESSION_PATH` must be writable by the runtime.
- `HTTP_PORT` is the built-in HTTP/WebSocket listener used for WebSocket upgrade traffic and direct local probes.
- `WASM_CORE_PATH` must point at the built `core.wasm` file.

After editing settings, restart UCE:

```bash
systemctl restart uce.service
```

## systemd service

For source-checkout deployments, install the provided service helper:

```bash
cd /opt/uce
scripts/systemd/manage-uce-service.sh setup
```

That helper installs `scripts/systemd/uce.service`, creates runtime directories, enables the service, and starts it.

Useful commands:

```bash
scripts/systemd/manage-uce-service.sh status
scripts/systemd/manage-uce-service.sh restart
scripts/systemd/manage-uce-service.sh logs 200
```

Equivalent manual systemd service for a source checkout:

```ini
[Unit]
Description=UCE FastCGI Runtime
After=network-online.target mariadb.service memcached.service
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=/opt/uce
RuntimeDirectory=uce
StateDirectory=uce
CacheDirectory=uce
ExecStartPre=/usr/bin/mkdir -p /var/cache/uce/work /var/lib/uce/uploads /var/lib/uce/sessions
ExecStartPre=/usr/bin/rm -f /run/uce/fastcgi.sock
ExecStartPre=/usr/bin/bash /opt/uce/scripts/build_linux.sh
ExecStart=/opt/uce/bin/uce_fastcgi.linux.bin
ExecStopPost=/usr/bin/rm -f /run/uce/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/uce.service` and run:

```bash
systemctl daemon-reload
systemctl enable --now uce.service
```

## 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
/examples/uce-starter/js/site.js
```

These should not touch the UCE runtime.

### Normal `.uce` page requests

For a request such as:

```text
GET /doc/index.uce?p=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 `.uce` file.
- `DOCUMENT_ROOT` — public web root, normally `<runtime-root>/site`.
- `SCRIPT_NAME` — URL path to the script, such as `/doc/index.uce`.
- `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.

UCE resolves the unit, compiles it to wasm if needed, creates a request workspace, and calls:

```cpp
RENDER(Request& context)
```

The unit writes output with template literals or `print()`. Response headers and status are set through `context.header` and `context.set_status()`.

### Component and sub-render calls

Inside a request, UCE code can call other units:

```cpp
component("components/card", props, context);
unit_render("other-page.uce", context);
```

These calls stay inside the UCE runtime. They are not new HTTP requests and do not go back through nginx or Apache.

### WebSocket pages

Any `.uce` unit can provide both an ordinary page render and WebSocket message handling:

```cpp
RENDER(Request& context) { ... }  // normal page load
WS(Request& context) { ... }      // later WebSocket messages
```

The nginx and Apache examples below split traffic by checking for a WebSocket upgrade request on `.uce` paths. A file such as `chat.uce` or `events.uce` can expose `WS(Request& context)`.

Routing split:

- Plain `GET /demo/chat.uce` should use FastCGI, just like any other page render.
- WebSocket upgrade requests for `/demo/chat.uce` should proxy to the UCE built-in HTTP/WebSocket listener at `HTTP_PORT`.

The built-in listener owns the socket lifecycle. When a message arrives, the broker forwards a render-style invocation back to the worker pool so `WS(Request& context)` runs inside the same wasm runtime model as normal pages.

### CLI requests

`CLI(Request& context)` handlers are not public web endpoints. They are invoked over `CLI_SOCKET_PATH`:

```bash
scripts/uce-cli /tests/cli.uce action=echo message=hello
curl --unix-socket /run/uce/cli.sock http://localhost/tests/cli.uce
```

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

UCE code can start local custom HTTP listeners with `server_start_http()`. Those are runtime-managed listeners for app-specific local services. They are separate from the public nginx/Apache entry point and should be firewalled or bound locally unless you explicitly want them reachable.

## 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.uce index.html;

    # Serve static files directly. Directory requests use index.uce when present.
    location / {
        try_files $uri $uri/ =404;
    }

    # UCE page requests use FastCGI. If the client asks to upgrade a .uce
    # request to WebSocket, send that connection to the built-in listener.
    location ~ \.uce$ {
        error_page 418 = @uce_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/uce/fastcgi.sock;
    }

    location @uce_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://127.0.0.1:8080;
    }

    # 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_PORT`.
- The example routes WebSocket upgrades for `.uce` paths to the HTTP/WebSocket listener.
- Ordinary `.uce` 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.uce`.

Front-controller variant:

```nginx
location / {
    try_files $uri $uri/ /index.uce?$query_string;
}
```

Reload nginx:

```bash
nginx -t
systemctl reload nginx
```

## Apache configuration

Apache can run UCE 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.uce 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

    # WebSocket upgrade traffic for any .uce unit goes to UCE's built-in HTTP listener.
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteCond %{REQUEST_URI} \.uce(?:\?|$) [NC]
    RewriteRule ^/(.*)$ ws://127.0.0.1:8080/$1 [P,L]

    # Normal .uce page loads go to FastCGI.
    <FilesMatch "\.uce$">
        SetHandler "proxy:unix:/run/uce/fastcgi.sock|fcgi://localhost/"
    </FilesMatch>

    # Optional: make the key CGI variables explicit for UCE.
    ProxyFCGISetEnvIf "true" DOCUMENT_ROOT "/var/www/html"
    ProxyFCGISetEnvIf "true" DOCUMENT_URI "%{REQUEST_URI}"
</VirtualHost>
```

Apache notes:

- `SetHandler "proxy:unix:/run/uce/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 `.uce` 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 UCE 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 `.uce` files instead:

```apache
ProxyPassMatch ^/(.*\.uce)$ unix:/run/uce/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/uce/fastcgi.sock`. Common approaches:

- run UCE 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/uce`. 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/uce/work
/var/lib/uce/uploads
/var/lib/uce/sessions
/run/uce
```

## Verification

Check service state:

```bash
systemctl status uce.service
journalctl -u uce.service -n 100 --no-pager
```

Check the FastCGI/web-server path:

```bash
curl -i http://127.0.0.1/doc/index.uce -H 'Host: example.com'
curl -i http://127.0.0.1/examples/uce-starter/ -H 'Host: example.com'
```

Check the local CLI path:

```bash
cd /opt/uce
scripts/uce-cli /tests/cli.uce action=echo message=hello
scripts/run_cli_tests.sh
```

Check WebSocket routing with a WebSocket client against a `.uce` endpoint that defines `WS(Request& context)` through nginx/Apache, not directly against `HTTP_PORT`.

## Troubleshooting

### 502 Bad Gateway

Check:

- `systemctl status uce.service`
- `journalctl -u uce.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 `.uce` file

### Raw `.uce` source is downloaded or displayed

The `.uce` 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`
- `.uce` upgrade traffic reaches `HTTP_PORT`
- nginx/Apache preserves `Upgrade` and `Connection` headers
- firewall/network policy allows localhost access to `HTTP_PORT`

### Page compiles fail

Check the compile artifact paths shown in the UCE error response and service logs. Generated files and compile output live under `BIN_DIRECTORY`. A safe last-resort recovery step is to stop UCE, move the affected generated artifact directory aside, and restart so the runtime recompiles from source.

### CLI commands fail

Check:

- `CLI_SOCKET_PATH` in `/etc/uce/settings.cfg`
- `/run/uce/cli.sock` exists
- `scripts/uce-cli --socket /run/uce/cli.sock /ping` works
- the target unit defines `CLI(Request& context)`
