Сервис publish-api слушает отдельный порт (PUBLISH_API_PORT, по умолчанию 9032) в том же контейнере, что и Caddy. Все JSON-ответы (успех и ошибки) отдаются с заголовком Content-Type: application/json; charset=utf-8.

Переменные окружения#

ПеременнаяНазначение
PUBLISH_HUB_PUBLIC_URLURL портала в браузере (HTTPS Caddy, часто порт HTTPS_PORT). Не URL publish-api (другой порт). Без завершающего /. Нужна для GET /v1/build-config.
PUBLISH_API_TOKENSПравила TOKEN=CIDR1,CIDR2 через ;. Пусто после = — токен с любого IP (только в доверенной сети).
PUBLISH_API_TRUST_X_HEADERStrue (по умолчанию): для проверки 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-confighugo --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=…&section=…&format=text и передавать в hugo --baseURL. Либо вручную собрать тот же префикс {PUBLISH_HUB_PUBLIC_URL}/docs/{slug}/{section}/.

При несовпадении API в ответе POST /v1/publish может быть массив warnings.