Сервис publish-api слушает отдельный порт (PUBLISH_API_PORT, по умолчанию 9032) в том же контейнере, что и Caddy. Все JSON-ответы (успех и ошибки) отдаются с заголовком Content-Type: application/json; charset=utf-8.
Переменные окружения#
| Переменная | Назначение |
|---|---|
PUBLISH_HUB_PUBLIC_URL | URL портала в браузере (HTTPS Caddy, часто порт HTTPS_PORT). Не URL publish-api (другой порт). Без завершающего /. Нужна для GET /v1/build-config. |
PUBLISH_API_TOKENS | Правила TOKEN=CIDR1,CIDR2 через ;. Пусто после = — токен с любого IP (только в доверенной сети). |
PUBLISH_API_TRUST_X_HEADERS | true (по умолчанию): для проверки CIDR использовать X-Forwarded-For / X-Real-IP. false: только TCP-адрес (RemoteAddr). |
PUBLISH_API_MAX_ZIP_MB | Лимит размера ZIP, МиБ. |
Подробнее — файл CONFIGURATION.md в корне репозитория EasyDocs.
Почему «forbidden for this network» с localhost#
Запрос с вашего ПК на http://localhost:9032 попадает в контейнер через проброс портов Docker. Для процесса внутри контейнера исходный IP часто не 127.0.0.1, а адрес шлюза Docker (например 172.17.0.1 или подсеть 172.16.0.0/12). В PUBLISH_API_TOKENS нужно перечислить соответствующие CIDR (в .env.example для локальной разработки уже добавлен 172.16.0.0/12 вместе с 127.0.0.1/32 и ::1/128).
Если клиент (или прокси) подставляет неверный X-Forwarded-For, при PUBLISH_API_TRUST_X_HEADERS=true проверка может использовать «чужой» IP. Задайте PUBLISH_API_TRUST_X_HEADERS=false, чтобы опираться только на TCP-адрес.
GET /healthz#
- Авторизация: не требуется.
- Ответ:
200,Content-Type: text/plain, телоok.
GET /v1/build-config#
Зачем: в CI репозитория документации не хранить вручную полный baseURL для Hugo — хаб сам отдаёт строку с учётом домена и порта из PUBLISH_HUB_PUBLIC_URL.
- Авторизация: как у
POST /v1/publish(BearerилиX-API-Token). - Параметры запроса (query):
product_slug,section— те же значения, что потом вPOST /v1/publish. - Опционально:
format=text— ответ200,Content-Type: text/plain; charset=utf-8, тело ровно одна строкаbase_url(удобно подставить вhugo --baseURL "$(curl …)"безjq). - Если
PUBLISH_HUB_PUBLIC_URLна хабе не задан —503, JSON{"error":"…"}.
JSON-ответ (200, без format=text):
{
"base_url": "https://docs.example.com:9031/docs/my-product/api/",
"path_prefix": "/docs/my-product/api/",
"product_slug": "my-product",
"section": "api"
}Типовой CI: запрос build-config → hugo --minify --baseURL … → ZIP → POST /v1/publish. Шаблоны — ci-templates/gitlab-ci.yml и github-actions-product-docs.yml.
POST /v1/publish#
- Авторизация: заголовок
Authorization: Bearer <токен>илиX-API-Token: <токен>. - Тело:
multipart/form-data.
| Поле | Обязательно | Описание |
|---|---|---|
product_name | да | Отображаемое имя продукта на портале. |
product_slug | да | Каталог data/<slug>/..., только a-z, цифры, -. |
section | да | Подкаталог (api, guide, …), те же правила имени. |
description | нет | Текст карточки в products.yml. |
doc_title | нет | Подпись ссылки; по умолчанию из section (например API / Руководство). |
archive | да | Файл ZIP со статикой (корень с index.html). |
Успех (200): JSON, например:
{
"ok": true,
"path": "/docs/<slug>/<section>/",
"on_disk": "/srv/docs/<slug>/<section>",
"products": "/srv/docs/portal/data/products.yml"
}Ошибки: JSON {"error":"..."} с кодами 400, 401, 403, 413, 405, 500 по ситуации.
Коллекция Postman / Apidog#
Файл в репозитории: api/easydocs-publish-api.postman_collection.json. Импорт: Postman → Import; Apidog → импорт коллекции Postman.
Задайте переменные baseUrl (например http://localhost:9032) и token (как в .env).
Пример curl#
curl -sS -X POST "http://localhost:9032/v1/publish" \
-H "Authorization: Bearer change-me" \
-F "product_name=Demo" \
-F "product_slug=demo" \
-F "section=api" \
-F "archive=@site.zip;type=application/zip"Замените токен и путь к site.zip.
Внутри сайта 404, а главная открывается#
Хаб отдаёт файлы по URL /docs/<product_slug>/<section>/.... Hugo (и другие генераторы) вшивают в HTML абсолютные пути из baseURL. Если при сборке было, например, --baseURL https://docs.example.com/docs/jforge/guide/, а в API указали product_slug=test, то в ZIP ссылки останутся /docs/jforge/..., а на диске лежит test/guide/ — переходы ведут в несуществующие пути → «страница не найдена».
Что сделать: в CI брать URL с хаба — GET /v1/build-config?product_slug=…§ion=…&format=text и передавать в hugo --baseURL. Либо вручную собрать тот же префикс {PUBLISH_HUB_PUBLIC_URL}/docs/{slug}/{section}/.
При несовпадении API в ответе POST /v1/publish может быть массив warnings.