view man/d2obase.5.md @ 8:2ffb8028ccbb

add loggingmodes and fix path matching
author Atarwn Gard <a@qwa.su>
date Tue, 17 Mar 2026 19:55:07 +0500
parents 54ab94198677
children
line wrap: on
line source

% D2OBASE(5)
% March 2026

# NAME
d2obase - syntax and configuration guide for the d2o web server

# DESCRIPTION
The **ICF** (Inheritance Config Format) uses a stream-oriented approach to web serving. It focuses on reducing "boilerplate" by using variables, reusable mixins, and execution pipelines.

# SYNTAX
A configuration file consists of variable assignments, mixin definitions, and site blocks containing pipelines.

## Variables
Variables are defined as `KEY=VALUE`. They are global and can be referenced later using the `$` prefix (e.g., `$DOM`).

Variable names may only contain characters `[A-Za-z0-9_]`. Values are substituted at the point of declaration, so a variable can reference only those variables defined above it in the file.

## Mixins (Templates)
A mixin is defined with the `@` prefix. It stores a set of pipeline directives that can be "splatted" into a site block to avoid repetition.

    @name
    |> directive argument

Mixins can inherit from other mixins:

    @child @parent
    |> directive argument

The parent's directives are inserted before the child's, so the child can override them. Circular references are silently ignored.

## Pipelines and Directives
The core of the configuration is the pipeline. Each line starting with `|>` is a directive executed in top-down order.

Directives follow the format: `|> directive [arguments...]`.

### Argument Expansion
Directives support curly brace expansion `{}`. For example:
`file.{crt,key}` expands to two separate arguments: `file.crt` and `file.key`.

Only one pair of braces per argument is expanded. Nested braces are not supported.

## Site Blocks
A site block starts with a domain name (or a variable containing one), followed by optional mixins, and a pipeline.

    example.org @setup
    |> directive argument

### Pattern Matching
The domain part of a site block is matched exactly. The path part (if present, separated by `/`) is matched by prefix.

Named capture groups can be used in patterns with `<name>` syntax. The captured value is available as `$name` in the block's directives.

    <sub>.example.org
    |> root /srv/www/$sub

#### Block Specificity

When multiple blocks match a request, the most specific one wins. Specificity is the number of literal characters matched in the pattern - capture groups `<n>` do not contribute to the score. Host and path are scored separately; host score is weighted heavier (`score = host_score × 1000 + path_score`).

For example, given these two blocks:

    app.example.org
    |> rprx http://127.0.0.1:8080

    <sub>.example.org
    |> root /srv/www/$sub

A request for `app.example.org` matches both, but `app.example.org` wins - it matched 15 literal characters against 11 for `<sub>.example.org` (`.example.org` only). Order of declaration in the file does not matter.

## Comments
Lines beginning with `;` are ignored. Inline comments start with ` ;` (space before semicolon).

    ; full line comment
    KEY=value  ; inline comment

# DIRECTIVES

## Listening

**port** *number*
: Listen for plain HTTP on the given port.

**port+tls** *number* [*cert* *key*]
: Listen for HTTPS on the given port. Certificate and key paths are optional if a **tls** directive is present in the same block.

**tls** *cert* *key*
: Set the default certificate and key for **port+tls** directives in this block that do not specify their own paths.

## Serving

**root** *path* [**show**]
: Serve static files from *path*. Without **show**, directory listing is forbidden (403). With **show**, an HTML directory listing is returned.

**ndex** *file...*
: List of index filenames to try when a directory is requested, checked left to right. If a matching file passes the **fcgi** pattern, it is handled by FastCGI instead of served directly.

**fcgi** *address* [*pattern*]
: Forward matching requests to a FastCGI server. *address* is either `unix:///path/to/socket` or `host:port`. *pattern* is a glob matched against the request path (default `*`). When used together with **root**, only requests matching the pattern are forwarded; everything else is served as static.

**rprx** *address*
: Reverse-proxy all requests to *address*. The `http://` scheme is assumed if not specified.

**rdir** *code* *url*
: Redirect to *url* with the given HTTP status code. If *code* is omitted or zero, 302 is used.

## Global (in `@d2o` block only)

**threads** *n*
: Set `GOMAXPROCS` to *n*.

# EXAMPLES
Below is a complex setup using global variables, a shared mixin for PHP sites, and subdomain handling:

    DOM=qwaderton.org
    ACME=/etc/acme
    WWW=/srv/www/$DOM

    @ports
    |> port 80
    |> port+tls 443 $ACME/$DOM.{crt,key}
    |> ndex index.php index.html

    @ports+fcgi @ports
    |> fcgi unix:///run/php-fpm.sock *.php

    $DOM @ports+fcgi
    |> root $WWW show

    <sub>.$DOM @ports+fcgi
    |> root $WWW/$sub

    app.$DOM @ports
    |> rprx http://127.0.0.1:8080

    qwa.su
    |> port 80
    |> port+tls 443 $ACME/qwa.su.{crt,key}
    |> rdir 307 https://qwaderton.org/

We use this configuration on our web server (as it is at the time of writing this manual).

If you need something simplier to start quickly, use this config:

    DOM=mydomain
    TLS=/etc/acme/$DOM
    WWW=/srv/www/$DOM

    $DOM
    |> port 80
    |> port+tls 443 $TLS.{crt,key}
    |> root $WWW show

# CAVEATS
1. ICF does not support strings with spaces - there are **no quotes or escapes**. Every character except space is treated as part of a token, including `!`, `*`, `/`, and so on. Glob patterns passed to **fcgi** are forwarded as-is to the server.

2. Variables are evaluated top-down at the point of declaration. Forward references do not work: if `B=$A` appears before `A=value`, `B` will contain the literal `$A`.

3. Only one pair of curly braces is expanded per argument. `file.{a,b}.{x,y}` does not produce four arguments - only the first `{}` pair is expanded.

4. A directive outside of any block (a `|>` line before the first block header) is a parse error and will prevent the server from starting.

5. Duplicate ports across blocks are silently deduplicated - the first declaration wins. TLS certificate paths from a later block with the same port are ignored.

# SEE ALSO
**d2o**(1)