view README.md @ 4:dacc92aae6d5

expand vars in vars
author Atarwn Gard <a@qwa.su>
date Mon, 09 Mar 2026 03:07:18 +0500
parents 3e7247db5c6e
children
line wrap: on
line source

# d2o

Минималистичный веб-сервер на Go с конфигурацией в формате ICF.

## Структура проекта

```
d2o/
  go.mod
  cmd/d2o/
    main.go       — точка входа, HTTP-обработчик, сборка листеров
  icf/
    icf.go        — парсер формата ICF (переиспользуемая библиотека)
  fcgi/
    fcgi.go       — минимальный FastCGI-клиент
```

## Сборка и запуск

```sh
go build -o d2o ./cmd/d2o/
./d2o                     # читает /etc/d2obase
./d2o /path/to/config     # альтернативный путь
```

---

## Формат конфигурации ICF

**ICF (Inherited Configuration Format)** — текстовый формат правил вида «паттерн → директивы».  
Вдохновлён синтаксисом bash и базами данных CoreDNS.

### Основные правила синтаксиса

```
; Это комментарий

; Переменная (без пробелов вокруг =)
KEY=value

; Абстрактный блок (миксин)
@name
|> directive arg1 arg2

; Конкретный блок с наследованием миксина
block.id @mixin
|> directive arg1 arg2

; Блок с именованным capture-группой
<sub>.example.com
|> root /srv/$sub
```

### Переменные

Объявляются как `KEY=value` на уровне файла. Подставляются через `$KEY` в аргументах директив.

```
WWW=/srv/www
CERT=/etc/acme/example.com

example.com
|> root $WWW/root
|> tls $CERT.{crt,key}
```

### Brace expansion

Аргумент `prefix.{a,b,c}` раскрывается в три отдельных аргумента: `prefix.a`, `prefix.b`, `prefix.c`.  
Порядок важен — используется для `tls`, где первый аргумент сертификат, второй ключ.

```
|> tls /etc/acme/example.com.{crt,key}
; эквивалентно:
|> tls /etc/acme/example.com.crt /etc/acme/example.com.key
```

### Capture-группы

В идентификаторе блока `<name>` захватывает любую подстроку до следующего литерала.  
`<_>` — анонимный wildcard, ничего не сохраняет.

```
<sub>.example.com
|> root /srv/$sub        ; $sub подставляется из захваченного значения

<_>.static.example.com
|> root /srv/static      ; совпадает с чем угодно, capture не нужен
```

Capture жадный слева: `<sub>.example.com` на запрос `a.b.example.com` даст `sub=a.b`.

### Кавычки в аргументах

Аргументы с пробелами берутся в двойные кавычки:

```
|> fcgi unix:/run/php-fpm.sock "*.php"
```

### Абстрактные блоки и наследование

`@name` задаёт набор директив без привязки к паттерну.  
Конкретный блок наследует их через `block.id @name` — директивы миксина идут первыми, директивы блока их перекрывают.

```
@base
|> port 80
|> port+tls 443

example.com @base
|> root /srv/www          ; наследует port 80 и port+tls 443

other.com @base
|> root /srv/other        ; то же самое
```

### Матчинг блоков

- Домен матчится точно (с учётом capture-групп).
- Путь матчится как префикс: блок `example.com/api` сработает на `/api/users`.
- При нескольких совпадениях выигрывает наиболее специфичный (больше совпавших литеральных символов).
- Сначала проверяется `host/path`, потом `host`.

```
example.com
|> root /srv/www

example.com/api
|> rprx 127.0.0.1:8080   ; перекрывает блок выше для /api/*
```

---

## Конфигурация d2o

Файл по умолчанию: `/etc/d2obase`

### @d2o — настройки сервера

```
@d2o
|> threads 512    ; GOMAXPROCS
```

| Директива  | Аргументы | Описание |
|------------|-----------|----------|
| `threads`  | N         | Количество потоков ОС (GOMAXPROCS) |

### port — HTTP-листенер

```
|> port 80
```

| # | Тип   | Описание     |
|---|-------|--------------|
| 1 | `int` | Номер порта  |

### port+tls — HTTPS-листенер

```
|> port+tls 443
; сертификат берётся из директивы tls того же блока
```

Знак `+` в имени директивы — обычный символ, не оператор.

| # | Тип    | Описание                                      |
|---|--------|-----------------------------------------------|
| 1 | `int`  | Номер порта                                   |
| 2 | `path` | Путь к сертификату (необязателен, если есть `tls`) |
| 3 | `path` | Путь к ключу (необязателен, если есть `tls`)  |

### tls — пути к сертификату и ключу

```
|> tls /etc/acme/example.com.{crt,key}
```

Используется как источник сертификата для `port+tls` в том же блоке, если пути не указаны явно.

| # | Тип    | Описание          |
|---|--------|-------------------|
| 1 | `path` | Путь к сертификату |
| 2 | `path` | Путь к ключу       |

### root — отдача статики

```
|> root /srv/www/example.com
|> root /srv/www/example.com hide       ; листинг запрещён (по умолчанию)
|> root /srv/www/example.com show       ; листинг разрешён, index-файл index.html
|> root /srv/www/example.com show index.php index.html   ; свои index-файлы
```

| # | Тип    | Описание                        |
|---|--------|---------------------------------|
| 1 | `path` | Корневая директория             |
| 2 | `show\|hide` | Режим директорий (по умолчанию `hide`) |
| 3–14 | `filename` | Index-файлы (только с `show`), проверяются по порядку |

При `show`: сначала ищутся index-файлы по списку, если ни один не найден — отдаётся листинг директории.  
При `hide` или без аргумента: запрос к директории возвращает 403.

### fcgi — FastCGI

```
|> fcgi unix:/run/php-fpm.sock
|> fcgi unix:/run/php-fpm.sock "*.php"   ; только .php файлы
|> fcgi 127.0.0.1:9000 "*.php"
```

| # | Тип     | Описание                                           |
|---|---------|----------------------------------------------------|
| 1 | `addr`  | Адрес сокета: `unix:/path` или `host:port`         |
| 2 | `glob`  | Паттерн файлов (по умолчанию `*`, все запросы)     |

Если glob не совпадает, запрос падает в `root` (если задан).

### rprx — обратный прокси

```
|> rprx 127.0.0.1:3000
|> rprx http://127.0.0.1:3000
```

| # | Тип   | Описание                        |
|---|-------|---------------------------------|
| 1 | `url` | Адрес backend (схема необязательна, по умолчанию `http://`) |

### Приоритет директив

При одновременном наличии нескольких директив порядок обработки: **rprx > fcgi > root**.

---

## Пример полного конфига

```
; /etc/d2obase

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

@d2o
|> threads 512

@ports
|> tls $ACME.{crt,key}
|> port 80
|> port+tls 443

qwaderton.org @ports
|> root $WWW/root show index.php index.html

qwaderton.org/webfeather @ports
|> root $WWW/root/webfeather
|> fcgi unix:/run/php-fpm.sock *.php

<sub>.qwaderton.org @ports
|> root $WWW/$sub
```

---

## Известные ограничения

- **Один миксин на блок.** Множественное наследование не поддерживается.
- **Brace expansion без вложенности.** `{a,{b,c}}` не работает.
- **FastCGI — один запрос на соединение.** Keep-alive с FPM не реализован.
- **Нет HTTP→HTTPS редиректа** из коробки — нужно реализовывать отдельным блоком на порту 80.
- **Нет hot reload** конфига — требуется перезапуск процесса.