Этап 0: скелет сервера, статика из embed, деплой на bare.xmatic.team
Один бинарь на stdlib, ноль зависимостей: подкоманды serve, vapid, version; статика из embed с ETag и Cache-Control: no-cache; /healthz; заголовки безопасности ADR-021 на всех ответах, включая ошибки; лимит тела 32 КиБ. Путь в логе — в percent-форме: подделать строку журнала запросом нельзя. Ни IP, ни ника, ни query в логах нет. Клиент — знак «Скобы» и слово bare: без script-тегов, inline-стилей и внешних ресурсов. ADR-025: go:embed не выходит за каталог пакета, поэтому объявление статики живёт в корне модуля, а клиент остаётся в web/. ADR-026: 413 отдаётся кодом too_large, неподдерживаемый метод — 404; оба добавлены в перечень protocol.md. Сервер: пользователь bare, /opt/bare, /var/lib/bare, /etc/bare/env 0600, systemd-юнит, nginx с сертификатом certbot (плагин nginx, как у соседей). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015DbCjVfTFq4ZFG8juD45YJ
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
# ADR-025: Встраивание статики объявляется в корне модуля
|
||||
|
||||
## Контекст
|
||||
|
||||
ADR-022 требует один артефакт деплоя: клиентская статика вкомпилирована в бинарь. Раскладка в `docs/plan.md` кладёт отдачу статики в `internal/web/`, а сам клиент — в `web/` в корне. Директива `//go:embed` встраивает только файлы каталога своего пакета и ниже: из `internal/web/` до корневого `web/` не дотянуться. Варианты — перенести клиент внутрь `internal/web/`, продублировать файлы или объявить встраивание в корне.
|
||||
|
||||
## Решение
|
||||
|
||||
Клиент остаётся в `web/` в корне: путь в репозитории совпадает с путём в URL, и его видно первым в дереве. Встраивание объявляется рядом — файл `embed.go` в корне модуля, `package bare`, `//go:embed web` и `var Web embed.FS`. Логики в пакете нет, только объявление.
|
||||
|
||||
`internal/web/` получает подкаталог через `fs.Sub(bare.Web, "web")` и отвечает за отдачу: ETag, `Cache-Control`, `Content-Type`, 304, `Service-Worker-Allowed`.
|
||||
|
||||
## Следствия
|
||||
|
||||
- В корне модуля появляется пакет `bare` из одного файла — он не растёт: всё, что не объявление `embed.FS`, идёт в `internal/`.
|
||||
- `internal/web/` импортирует корневой пакет; обратной зависимости нет и не будет.
|
||||
- Раскладка в `docs/plan.md` дополнена строкой `embed.go`; в остальном не меняется.
|
||||
@@ -0,0 +1,17 @@
|
||||
# ADR-026: Код `too_large` и 404 на неподдерживаемый метод
|
||||
|
||||
## Контекст
|
||||
|
||||
Этап 0 обнажил два места, где код знает больше протокола. Первое: `413` описан в общих правилах (`ADR-021`, «тело запроса — до 32 КиБ»), но кода ошибки для него в перечне `protocol.md` нет, а сервер уже отдаёт `{"error": "too_large"}`. Второе: `POST` к известному пути статики отвечает `404 not_found`; в перечне правил есть только «неизвестный путь — `404 not_found`», решение про метод жило комментарием в коде.
|
||||
|
||||
## Решение
|
||||
|
||||
- `413` отдаётся с кодом `too_large`. Код добавлен в перечень «Коды ошибок» `protocol.md`, строка про лимит тела уточнена до `413 too_large`.
|
||||
- Неподдерживаемый метод на известном пути — тоже `404 not_found`. Кода `405` в протоколе нет и не появится: клиент ходит по фиксированному набору маршрутов, а лишний код — лишняя ветка у обеих сторон.
|
||||
- Тело ошибки в форме `{"error", "message"}` собирает `internal/api`; остальные пакеты пользуются его хелпером, чтобы коды не расходились между пакетами.
|
||||
|
||||
## Следствия
|
||||
|
||||
- Клиент разбирает `error` по перечню из `protocol.md`, и перечень исчерпывающий.
|
||||
- Отсутствие `405` означает, что перебор методов не отличается от перебора путей — снаружи виден только `404`.
|
||||
- `internal/web` импортирует `internal/api`; обратной зависимости нет.
|
||||
@@ -16,6 +16,7 @@
|
||||
## Раскладка репозитория
|
||||
|
||||
```
|
||||
embed.go //go:embed web в корне модуля (ADR-025)
|
||||
cmd/bare/main.go подкоманды: serve, vapid, version
|
||||
internal/config/ переменные BARE_*
|
||||
internal/store/ SQLite, migrations/*.sql (embed), запросы
|
||||
|
||||
+3
-2
@@ -7,9 +7,10 @@ HTTP-API под `/api/`, JSON в обе стороны, `Content-Type: applicati
|
||||
- Аутентификация — cookie `bare_session` (ADR-021). Без неё — `401 unauthenticated`. Публичные: `GET /api/config`, `GET /api/kdf`, `POST /api/register`, `POST /api/login`.
|
||||
- На всех запросах кроме `GET`/`HEAD` заголовок `Origin` обязан равняться `BARE_ORIGIN`, иначе `403 bad_origin`.
|
||||
- Заголовок `X-Device: <deviceId>` обязателен на `/api/ack`, `/api/messages`, `/api/devices/{id}/push`; для `/api/events` устройство передаётся в query (`EventSource` не умеет заголовки). Устройство должно принадлежать пользователю сессии, иначе `403 unknown_device`.
|
||||
- Тело запроса — до 32 КиБ, иначе `413`.
|
||||
- Тело запроса — до 32 КиБ, иначе `413 too_large`.
|
||||
- Rate limiting — `429` с `Retry-After` (секунды).
|
||||
- Неизвестный путь — `404 not_found`; неверный JSON — `400 bad_json`; валидация — `400 invalid` с полем `field`.
|
||||
- Неподдерживаемый метод на известном пути — тоже `404 not_found`: кода `405` в протоколе нет (ADR-026).
|
||||
|
||||
## Типы
|
||||
|
||||
@@ -126,7 +127,7 @@ event: ready data: {}
|
||||
|
||||
## Коды ошибок
|
||||
|
||||
`unauthenticated`, `bad_origin`, `unknown_device`, `bad_json`, `invalid`, `invalid_nick`, `nick_taken`, `invite_required`, `invalid_invite`, `invalid_credentials`, `unknown_user`, `self`, `device_conflict`, `clock_skew`, `not_member`, `unknown_key`, `not_owner`, `owner`, `key_exists`, `keys_mismatch`, `not_found`, `rate_limited`.
|
||||
`unauthenticated`, `bad_origin`, `unknown_device`, `bad_json`, `invalid`, `invalid_nick`, `nick_taken`, `invite_required`, `invalid_invite`, `invalid_credentials`, `unknown_user`, `self`, `device_conflict`, `clock_skew`, `not_member`, `unknown_key`, `not_owner`, `owner`, `key_exists`, `keys_mismatch`, `not_found`, `rate_limited`, `too_large`.
|
||||
|
||||
## Статика и служебное
|
||||
|
||||
|
||||
Reference in New Issue
Block a user