|
1
|
1 # d2o
|
|
|
2
|
|
|
3 Минималистичный веб-сервер на Go с конфигурацией в формате ICF.
|
|
|
4
|
|
12
|
5 ## Сборка
|
|
|
6
|
|
|
7 Обязательные компоненты:
|
|
|
8 - go
|
|
|
9
|
|
|
10 Рекомендуемые:
|
|
|
11 - just
|
|
|
12 - lowdown
|
|
|
13
|
|
|
14 Сборка и установка:
|
|
|
15
|
|
|
16 ```
|
|
|
17 $ just build # просто сборка
|
|
|
18 $ just install # сборка и установка
|
|
|
19 ```
|
|
|
20
|
|
|
21 Если вы скачали только обязательные компоненты
|
|
|
22
|
|
|
23 ```
|
|
|
24 $ go build -o d2o .
|
|
|
25 ```
|
|
|
26
|
|
1
|
27 ## Структура проекта
|
|
|
28
|
|
|
29 ```
|
|
|
30 d2o/
|
|
|
31 go.mod
|
|
|
32 cmd/d2o/
|
|
|
33 main.go — точка входа, HTTP-обработчик, сборка листеров
|
|
|
34 icf/
|
|
|
35 icf.go — парсер формата ICF (переиспользуемая библиотека)
|
|
|
36 fcgi/
|
|
|
37 fcgi.go — минимальный FastCGI-клиент
|
|
|
38 ```
|
|
|
39
|
|
|
40 ## Сборка и запуск
|
|
|
41
|
|
|
42 ```sh
|
|
|
43 go build -o d2o ./cmd/d2o/
|
|
|
44 ./d2o # читает /etc/d2obase
|
|
|
45 ./d2o /path/to/config # альтернативный путь
|
|
|
46 ```
|
|
|
47
|
|
|
48 ---
|
|
|
49
|
|
|
50 ## Формат конфигурации ICF
|
|
|
51
|
|
|
52 **ICF (Inherited Configuration Format)** — текстовый формат правил вида «паттерн → директивы».
|
|
|
53 Вдохновлён синтаксисом bash и базами данных CoreDNS.
|
|
|
54
|
|
|
55 ### Основные правила синтаксиса
|
|
|
56
|
|
|
57 ```
|
|
|
58 ; Это комментарий
|
|
|
59
|
|
|
60 ; Переменная (без пробелов вокруг =)
|
|
|
61 KEY=value
|
|
|
62
|
|
|
63 ; Абстрактный блок (миксин)
|
|
|
64 @name
|
|
|
65 |> directive arg1 arg2
|
|
|
66
|
|
|
67 ; Конкретный блок с наследованием миксина
|
|
|
68 block.id @mixin
|
|
|
69 |> directive arg1 arg2
|
|
|
70
|
|
|
71 ; Блок с именованным capture-группой
|
|
|
72 <sub>.example.com
|
|
|
73 |> root /srv/$sub
|
|
|
74 ```
|
|
|
75
|
|
|
76 ### Переменные
|
|
|
77
|
|
|
78 Объявляются как `KEY=value` на уровне файла. Подставляются через `$KEY` в аргументах директив.
|
|
|
79
|
|
|
80 ```
|
|
|
81 WWW=/srv/www
|
|
|
82 CERT=/etc/acme/example.com
|
|
|
83
|
|
|
84 example.com
|
|
|
85 |> root $WWW/root
|
|
|
86 |> tls $CERT.{crt,key}
|
|
|
87 ```
|
|
|
88
|
|
|
89 ### Brace expansion
|
|
|
90
|
|
|
91 Аргумент `prefix.{a,b,c}` раскрывается в три отдельных аргумента: `prefix.a`, `prefix.b`, `prefix.c`.
|
|
|
92 Порядок важен — используется для `tls`, где первый аргумент сертификат, второй ключ.
|
|
|
93
|
|
|
94 ```
|
|
|
95 |> tls /etc/acme/example.com.{crt,key}
|
|
|
96 ; эквивалентно:
|
|
|
97 |> tls /etc/acme/example.com.crt /etc/acme/example.com.key
|
|
|
98 ```
|
|
|
99
|
|
|
100 ### Capture-группы
|
|
|
101
|
|
|
102 В идентификаторе блока `<name>` захватывает любую подстроку до следующего литерала.
|
|
|
103 `<_>` — анонимный wildcard, ничего не сохраняет.
|
|
|
104
|
|
|
105 ```
|
|
|
106 <sub>.example.com
|
|
|
107 |> root /srv/$sub ; $sub подставляется из захваченного значения
|
|
|
108
|
|
|
109 <_>.static.example.com
|
|
|
110 |> root /srv/static ; совпадает с чем угодно, capture не нужен
|
|
|
111 ```
|
|
|
112
|
|
|
113 Capture жадный слева: `<sub>.example.com` на запрос `a.b.example.com` даст `sub=a.b`.
|
|
|
114
|
|
|
115 ### Кавычки в аргументах
|
|
|
116
|
|
|
117 Аргументы с пробелами берутся в двойные кавычки:
|
|
|
118
|
|
|
119 ```
|
|
|
120 |> fcgi unix:/run/php-fpm.sock "*.php"
|
|
|
121 ```
|
|
|
122
|
|
|
123 ### Абстрактные блоки и наследование
|
|
|
124
|
|
|
125 `@name` задаёт набор директив без привязки к паттерну.
|
|
|
126 Конкретный блок наследует их через `block.id @name` — директивы миксина идут первыми, директивы блока их перекрывают.
|
|
|
127
|
|
|
128 ```
|
|
|
129 @base
|
|
|
130 |> port 80
|
|
|
131 |> port+tls 443
|
|
|
132
|
|
|
133 example.com @base
|
|
|
134 |> root /srv/www ; наследует port 80 и port+tls 443
|
|
|
135
|
|
|
136 other.com @base
|
|
|
137 |> root /srv/other ; то же самое
|
|
|
138 ```
|
|
|
139
|
|
|
140 ### Матчинг блоков
|
|
|
141
|
|
|
142 - Домен матчится точно (с учётом capture-групп).
|
|
|
143 - Путь матчится как префикс: блок `example.com/api` сработает на `/api/users`.
|
|
|
144 - При нескольких совпадениях выигрывает наиболее специфичный (больше совпавших литеральных символов).
|
|
|
145 - Сначала проверяется `host/path`, потом `host`.
|
|
|
146
|
|
|
147 ```
|
|
|
148 example.com
|
|
|
149 |> root /srv/www
|
|
|
150
|
|
|
151 example.com/api
|
|
|
152 |> rprx 127.0.0.1:8080 ; перекрывает блок выше для /api/*
|
|
|
153 ```
|
|
|
154
|
|
|
155 ---
|
|
|
156
|
|
|
157 ## Конфигурация d2o
|
|
|
158
|
|
|
159 Файл по умолчанию: `/etc/d2obase`
|
|
|
160
|
|
|
161 ### @d2o — настройки сервера
|
|
|
162
|
|
|
163 ```
|
|
|
164 @d2o
|
|
|
165 |> threads 512 ; GOMAXPROCS
|
|
|
166 ```
|
|
|
167
|
|
|
168 | Директива | Аргументы | Описание |
|
|
|
169 |------------|-----------|----------|
|
|
|
170 | `threads` | N | Количество потоков ОС (GOMAXPROCS) |
|
|
|
171
|
|
|
172 ### port — HTTP-листенер
|
|
|
173
|
|
|
174 ```
|
|
|
175 |> port 80
|
|
|
176 ```
|
|
|
177
|
|
|
178 | # | Тип | Описание |
|
|
|
179 |---|-------|--------------|
|
|
|
180 | 1 | `int` | Номер порта |
|
|
|
181
|
|
|
182 ### port+tls — HTTPS-листенер
|
|
|
183
|
|
|
184 ```
|
|
|
185 |> port+tls 443
|
|
|
186 ; сертификат берётся из директивы tls того же блока
|
|
|
187 ```
|
|
|
188
|
|
|
189 Знак `+` в имени директивы — обычный символ, не оператор.
|
|
|
190
|
|
|
191 | # | Тип | Описание |
|
|
|
192 |---|--------|-----------------------------------------------|
|
|
|
193 | 1 | `int` | Номер порта |
|
|
|
194 | 2 | `path` | Путь к сертификату (необязателен, если есть `tls`) |
|
|
|
195 | 3 | `path` | Путь к ключу (необязателен, если есть `tls`) |
|
|
|
196
|
|
|
197 ### tls — пути к сертификату и ключу
|
|
|
198
|
|
|
199 ```
|
|
|
200 |> tls /etc/acme/example.com.{crt,key}
|
|
|
201 ```
|
|
|
202
|
|
|
203 Используется как источник сертификата для `port+tls` в том же блоке, если пути не указаны явно.
|
|
|
204
|
|
|
205 | # | Тип | Описание |
|
|
|
206 |---|--------|-------------------|
|
|
|
207 | 1 | `path` | Путь к сертификату |
|
|
|
208 | 2 | `path` | Путь к ключу |
|
|
|
209
|
|
|
210 ### root — отдача статики
|
|
|
211
|
|
|
212 ```
|
|
|
213 |> root /srv/www/example.com
|
|
|
214 |> root /srv/www/example.com hide ; листинг запрещён (по умолчанию)
|
|
|
215 |> root /srv/www/example.com show ; листинг разрешён, index-файл index.html
|
|
|
216 |> root /srv/www/example.com show index.php index.html ; свои index-файлы
|
|
|
217 ```
|
|
|
218
|
|
|
219 | # | Тип | Описание |
|
|
|
220 |---|--------|---------------------------------|
|
|
|
221 | 1 | `path` | Корневая директория |
|
|
|
222 | 2 | `show\|hide` | Режим директорий (по умолчанию `hide`) |
|
|
|
223 | 3–14 | `filename` | Index-файлы (только с `show`), проверяются по порядку |
|
|
|
224
|
|
|
225 При `show`: сначала ищутся index-файлы по списку, если ни один не найден — отдаётся листинг директории.
|
|
|
226 При `hide` или без аргумента: запрос к директории возвращает 403.
|
|
|
227
|
|
|
228 ### fcgi — FastCGI
|
|
|
229
|
|
|
230 ```
|
|
|
231 |> fcgi unix:/run/php-fpm.sock
|
|
|
232 |> fcgi unix:/run/php-fpm.sock "*.php" ; только .php файлы
|
|
|
233 |> fcgi 127.0.0.1:9000 "*.php"
|
|
|
234 ```
|
|
|
235
|
|
|
236 | # | Тип | Описание |
|
|
|
237 |---|---------|----------------------------------------------------|
|
|
|
238 | 1 | `addr` | Адрес сокета: `unix:/path` или `host:port` |
|
|
|
239 | 2 | `glob` | Паттерн файлов (по умолчанию `*`, все запросы) |
|
|
|
240
|
|
|
241 Если glob не совпадает, запрос падает в `root` (если задан).
|
|
|
242
|
|
|
243 ### rprx — обратный прокси
|
|
|
244
|
|
|
245 ```
|
|
|
246 |> rprx 127.0.0.1:3000
|
|
|
247 |> rprx http://127.0.0.1:3000
|
|
|
248 ```
|
|
|
249
|
|
|
250 | # | Тип | Описание |
|
|
|
251 |---|-------|---------------------------------|
|
|
|
252 | 1 | `url` | Адрес backend (схема необязательна, по умолчанию `http://`) |
|
|
|
253
|
|
|
254 ### Приоритет директив
|
|
|
255
|
|
|
256 При одновременном наличии нескольких директив порядок обработки: **rprx > fcgi > root**.
|
|
|
257
|
|
|
258 ---
|
|
|
259
|
|
|
260 ## Пример полного конфига
|
|
|
261
|
|
|
262 ```
|
|
|
263 ; /etc/d2obase
|
|
|
264
|
|
|
265 ACME=/etc/acme/qwaderton.org
|
|
|
266 WWW=/srv/www/qwaderton.org
|
|
|
267
|
|
|
268 @d2o
|
|
|
269 |> threads 512
|
|
|
270
|
|
|
271 @ports
|
|
|
272 |> tls $ACME.{crt,key}
|
|
|
273 |> port 80
|
|
|
274 |> port+tls 443
|
|
|
275
|
|
|
276 qwaderton.org @ports
|
|
|
277 |> root $WWW/root show index.php index.html
|
|
|
278
|
|
|
279 qwaderton.org/webfeather @ports
|
|
|
280 |> root $WWW/root/webfeather
|
|
|
281 |> fcgi unix:/run/php-fpm.sock *.php
|
|
|
282
|
|
|
283 <sub>.qwaderton.org @ports
|
|
|
284 |> root $WWW/$sub
|
|
|
285 ```
|
|
|
286
|
|
|
287 ---
|
|
|
288
|
|
|
289 ## Известные ограничения
|
|
|
290
|
|
|
291 - **Один миксин на блок.** Множественное наследование не поддерживается.
|
|
|
292 - **Brace expansion без вложенности.** `{a,{b,c}}` не работает.
|
|
|
293 - **FastCGI — один запрос на соединение.** Keep-alive с FPM не реализован.
|
|
|
294 - **Нет HTTP→HTTPS редиректа** из коробки — нужно реализовывать отдельным блоком на порту 80.
|
|
12
|
295 - **Нет hot reload** конфига — требуется перезапуск процесса.
|