# Bootstrap кластера

## Назначение

`bootstrap/**` задаёт один активный путь установки MVP: зайти на сервер,
подготовить минимальный `bootstrap/host/config.env` и запустить локальный
installer.

Контур поднимает и проверяет:

- k3s на текущем сервере;
- kubeconfig для `OPERATOR_USER`;
- kubelet image GC и host image prune timer;
- `/opt/kodex` как локальный snapshot репозитория для install;
- internal registry foundation;
- проверка registry mirror и Kaniko build.

## Файлы

| Путь | Назначение |
|---|---|
| `bootstrap/host/bootstrap_cluster.sh` | Единственная активная точка входа: `preflight`, `install`, `--dry-run`. |
| `bootstrap/host/plan_backend_deploy.sh` | План первого backend deploy только на чтение: инвентарь, рендер, `kubectl kustomize` и проверки кластера без изменений. |
| `bootstrap/host/deploy_backend_ring.sh` | Реальное применение backend-колец, `staff-gateway`, `platform-mcp-server`, `web-console` и публичного web-contour: registry, Kubernetes `Secret`, Kaniko-сборки, PostgreSQL, миграции и сервисы выбранного кольца. |
| `bootstrap/local/install.sh` | Локальный privileged orchestrator install-шагов. |
| `bootstrap/local/steps/*.sh` | Узкие idempotent host/Kubernetes steps. |
| `bootstrap/host/smoke_registry_kaniko.sh` | Проверка registry mirror и Kaniko build/push без Docker daemon. |
| `bootstrap/host/smoke_backend_contour.sh` | Тонкая обвязка проверки над `deploy_backend_ring.sh --skip-build`. |
| `deploy/base/bootstrap-foundation/**` | Manifests внутреннего registry. |
| `deploy/base/bootstrap-builder-smoke/**` | Manifests заданий проверки mirror/Kaniko. |
| `deploy/base/web-public-foundation/**` | Manifests Traefik `IngressClass`/controller и `ClusterIssuer` Let’s Encrypt. |
| `deploy/base/web-console-public/**` | Manifests `oauth2-proxy`, `Certificate` и публичного `Ingress` для `web-console`. |
| `deploy/base/integration-gateway-public/**` | Public `Ingress` для GitHub webhook route `integration-gateway`; защита только HMAC, не OAuth. |
| `cmd/bootstrap-preflight` | Безопасный preflight: имена env, stack inventory, dry-run рендер, kustomize и проверки Kubernetes только на чтение. |
| `cmd/bootstrap-deploy-plan` | Безопасный план backend deploy: инвентарь MVP-сервисов, PostgreSQL/event-log manifests, service manifests, builder refs и проверки foundation только на чтение. |
| `cmd/bootstrap-operational-acceptance` | Read-only операционная приёмка после deploy: Kubernetes workloads, jobs, secrets, registry, public web/webhook contour, HTTPS/OAuth redirect и HMAC-only webhook reject без раскрытия значений env. |
| `cmd/manifest-render` | Stack-aware renderer: читает `services.yaml`, затем применяет env overrides. |
| `libs/go/stackinventory` | Общая Go-библиотека чтения корневого stack inventory для renderer/install/deploy tools. |
| `libs/go/manifestrender` | Общая Go-библиотека рендера manifest templates поверх `stackinventory`. |
| `bootstrap/host/config.env.example` | Минимальный пример env для локальной установки. |

## Подготовка env

```bash
cp bootstrap/host/config.env.example bootstrap/host/config.env
```

Заполните `bootstrap/host/config.env`. Домены, адреса, email, токены, ключи,
пароли, DSN и kubeconfig считаются чувствительными: не публикуйте их в Issue,
PR, логах и документации.

Минимально важные параметры:

- `OPERATOR_USER`;
- `KODEX_PRODUCTION_NAMESPACE`;
- `KODEX_PRODUCTION_DOMAIN`;
- `KODEX_PUBLIC_BASE_URL`;
- `KODEX_LETSENCRYPT_EMAIL`;
- `KODEX_BOOTSTRAP_PUBLIC_HOST`, если DNS нужно сверять с публичным host/IP, а не с локальными адресами сервера;
- `KODEX_INTERNAL_REGISTRY_*`;
- `KODEX_AGENT_MANAGER_SELF_DEPLOY_SIGNAL_PROJECT_ID` после штатного onboarding/adoption self-repo;
- `KODEX_AGENT_MANAGER_SELF_DEPLOY_SIGNAL_CONSUMER_ENABLED=true`;
- `KODEX_AGENT_MANAGER_SELF_DEPLOY_GOVERNANCE_GATE_ENABLED=true`;
- `KODEX_AGENT_MANAGER_SELF_DEPLOY_BUILD_DISPATCH_ENABLED=true`;
- `KODEX_SSH_PORT`, если host firewall включён;
- пустые runtime secret seeds, которые bootstrap сгенерирует при install.

Версии и дефолтные имена образов берутся из `services.yaml`. Env-переменные
вроде `KODEX_REGISTRY_IMAGE`, `KODEX_KANIKO_EXECUTOR_IMAGE` и
`KODEX_IMAGE_MIRROR_TOOL_IMAGE` являются override-слоем, а не вторым источником
версий.

Правило defaults:

- корневой `services.yaml` задаёт версии, образы, deploy inventory и стандартные имена артефактов платформенного стека;
- это не проектный `services.yaml`: пользовательской project policy, импортом и проверенной проекцией владеет `project-catalog`;
- Go-инструменты читают корневой stack inventory через `libs/go/stackinventory`, а не через собственный YAML/awk/parser слой;
- Go config сервиса задаёт безопасные runtime defaults самого сервиса;
- Kubernetes templates не повторяют runtime defaults сервиса как `envOr`, если сервис уже имеет такой default;
- `bootstrap/host/config.env.example` хранит только локальные install-настройки и secret/bootstrap seed-поля.

Готовность `self-deploy` проверяется до `rollout`. `cmd/bootstrap-deploy-plan`
падает, если у `agent-manager` нет `KODEX_AGENT_MANAGER_SELF_DEPLOY_SIGNAL_PROJECT_ID`,
если выключен signal consumer, governance gate или build dispatch, либо если
отрендеренный manifest потерял обязательные env/Secret refs. При проверке с
`--require-kubernetes` дополнительно проверяется наличие нужных ключей в
`kodex-platform-runtime`; значения ключей не печатаются.

## Preflight и dry-run

```bash
bash bootstrap/host/bootstrap_cluster.sh preflight --env-file bootstrap/host/config.env
```

План без запуска install-шагов:

```bash
bash bootstrap/host/bootstrap_cluster.sh install --env-file bootstrap/host/config.env --dry-run
```

Preflight проверяет ОС, root/sudo, базовые команды, k3s/kubectl при наличии,
DNS prerequisite, наличие bootstrap manifests и обязательные env-поля. Проверка
не печатает значения env, домены или адреса.

Если `go` доступен, preflight дополнительно запускает `cmd/bootstrap-preflight`.
Этот шаг:

- загружает `bootstrap/host/config.env`, но печатает только имена проверок, без значений;
- читает root `services.yaml` через `libs/go/stackinventory`;
- разрешает registry/Kaniko/crane/busybox image refs через stack inventory и env override-слой;
- рендерит `bootstrap-foundation` и `bootstrap-builder-smoke` через `libs/go/manifestrender`;
- выполняет `kubectl kustomize`, если `kubectl` доступен;
- выполняет проверки Kubernetes только на чтение: context, `/readyz`, namespace, `kodex-registry` Deployment/Service.

Если `go` ещё не установлен на чистом host, shell preflight фиксирует deferred
status: `00_prepare_host.sh` установит Go перед install-шагами, а dry-run
render можно повторить после подготовки host.

Для строгой проверки уже установленного кластера:

```bash
bash bootstrap/host/bootstrap_cluster.sh preflight \
  --env-file bootstrap/host/config.env \
  --require-kubernetes
```

Без `--require-kubernetes` отсутствующий `kubectl`, namespace или registry
фиксируется как deferred check: install или foundation smoke подготовят их позже.

## Установка

Установка запускается только на сервере, где должен жить Kubernetes:

```bash
bash bootstrap/host/bootstrap_cluster.sh install --env-file bootstrap/host/config.env
```

Install выполняет шаги:

1. preflight;
2. подготовка ОС и системных пакетов;
3. создание `OPERATOR_USER`;
4. установка или проверка k3s;
5. настройка `/etc/rancher/k3s/registries.yaml` на internal registry;
6. настройка kubelet image GC и host image prune timer;
7. проверка network prerequisites без установки публичного web-contour;
8. доставка snapshot репозитория в `/opt/kodex`;
9. подготовка runtime env без печати секретов;
10. render/apply internal registry foundation;
11. включение host firewall baseline;
12. итоговый отчёт.

Архив репозитория исключает `.git`, `.local` и `bootstrap/host/*.env`; runtime
env передаётся отдельно.

## Dry-run план backend deploy

После preflight, до любого реального `kubectl apply`, запуска jobs или сборки
образов, оператор строит план первого backend deploy только на чтение:

```bash
bash bootstrap/host/plan_backend_deploy.sh --env-file bootstrap/host/config.env
```

Команда не меняет кластер. Она:

- читает `services.yaml` через `libs/go/stackinventory`;
- проверяет deploy inventory MVP-сервисов, Dockerfile, service/migration
  manifests и зависимости по именам сервисов;
- разрешает image refs через stack inventory и env override-слой, но не печатает
  значения registry, доменов или secret env;
- рендерит `deploy/base/postgres/**`,
  `deploy/base/platform-event-log/migrations.yaml.tpl`,
  `deploy/base/bootstrap-foundation/**`,
  `deploy/base/bootstrap-builder-smoke/**` и все текущие deployable service
  manifests;
- выполняет `kubectl kustomize` для отрендеренных manifest sets, если доступен
  `kubectl`;
- выполняет проверки текущего Kubernetes foundation только на чтение: context,
  `/readyz`, namespace, registry Deployment/Service, PostgreSQL
  StatefulSet/Service и runtime Secret refs.

Для проверки только render/inventory без чтения Kubernetes:

```bash
bash bootstrap/host/plan_backend_deploy.sh \
  --env-file bootstrap/host/config.env \
  --skip-live-kubernetes
```

Для уже установленного foundation-контура можно включить строгий режим:

```bash
bash bootstrap/host/plan_backend_deploy.sh \
  --env-file bootstrap/host/config.env \
  --require-kubernetes
```

Если нужен каталог с отрендеренными файлами для ручной проверки, передайте
пустой `--render-dir`. Непустой каталог отклоняется; команда не удаляет пути,
переданные оператором.

## Реальный deploy backend-колец, staff-gateway, platform-mcp-server и web-console

После успешного preflight и dry-run плана оператор может применить backend-кольцо.
Без `--ring` используется первое кольцо, чтобы существующий путь не менял
поведение:

```bash
bash bootstrap/host/deploy_backend_ring.sh --env-file bootstrap/host/config.env
```

Второе кольцо запускается явно:

```bash
bash bootstrap/host/deploy_backend_ring.sh \
  --env-file bootstrap/host/config.env \
  --ring second
```

Для новой установки, где нужно последовательно применить оба backend-кольца
одной командой, используется `all`. Этот режим не включает `staff-gateway`,
`platform-mcp-server` и `web-console`:

```bash
bash bootstrap/host/deploy_backend_ring.sh \
  --env-file bootstrap/host/config.env \
  --ring all
```

После успешного второго кольца `staff-gateway` запускается отдельным контуром:

```bash
bash bootstrap/host/deploy_backend_ring.sh \
  --env-file bootstrap/host/config.env \
  --ring staff
```

После готовности сервисов-владельцев MCP-поверхность запускается отдельным
контуром:

```bash
bash bootstrap/host/deploy_backend_ring.sh \
  --env-file bootstrap/host/config.env \
  --ring mcp
```

После готовности `staff-gateway` фронтенд запускается отдельным контуром:

```bash
bash bootstrap/host/deploy_backend_ring.sh \
  --env-file bootstrap/host/config.env \
  --ring web
```

Публичный HTTPS-доступ к фронтенду запускается отдельным контуром после
готовности `web-console` и `staff-gateway`:

```bash
bash bootstrap/host/deploy_backend_ring.sh \
  --env-file bootstrap/host/config.env \
  --ring web-public
```

`web-public` устанавливает или проверяет `cert-manager`, применяет Traefik
`IngressClass` `kodex-public`, создаёт `ClusterIssuer` Let’s Encrypt, готовит
`Secret` `kodex-web-oauth2-proxy`, разворачивает `oauth2-proxy`, выпускает
`Certificate` для `platform.kodex.works`, создаёт публичный `Ingress` только на
`oauth2-proxy` для UI и отдельный HMAC-only `Ingress` на
`/v1/provider-webhooks/github` для `integration-gateway`. Прямой публичный
`Ingress` на `web-console` запрещён: если OAuth secret не может быть создан из
существующего Kubernetes `Secret` или env seed-полей, команда завершается до
создания публичных `Ingress`.

Состав колец:

- `first`: `access-manager`, `project-catalog`, `package-hub`, `provider-hub`;
- `second`: `fleet-manager`, `runtime-manager`, `interaction-hub`,
  `governance-manager`, `agent-manager`, `integration-gateway`,
  `codex-hook-ingress`.
- `staff`: `staff-gateway`.
- `mcp`: `platform-mcp-server`.
- `web`: `web-console`.
- `web-public`: Traefik/cert-manager foundation, `oauth2-proxy` перед
  `web-console` и HMAC-only GitHub webhook route в `integration-gateway`.

`staff-gateway`, `platform-mcp-server`, `web-console` и `web-public` не входят в
`all`, чтобы повторный backend deploy не менял edge-, MCP-, frontend- и public
web-contour без явного выбора оператора.

Команда выполняет изменения в Kubernetes и остаётся идемпотентной:

- создаёт namespace через `kubectl apply`;
- для workload-колец применяет internal registry foundation и ждёт readiness;
- для workload-колец читает существующие `kodex-postgres` и
  `kodex-platform-runtime` `Secret`;
- генерирует только отсутствующие ключи `Secret` и сохраняет их в Kubernetes до
  запуска workloads или публичного `oauth2-proxy`;
- не перегенерирует уже существующие значения `Secret`;
- для workload-колец нормализует локальные PostgreSQL DSN, если они не
  соответствуют текущему паролю из `kodex-postgres`;
- рендерит manifests во временный приватный каталог и удаляет его после работы;
- собирает через Kaniko образы выбранного кольца и migration-образы без Docker
  daemon;
- для backend/staff/MCP-контуров применяет PostgreSQL foundation, создаёт базы
  через `kodex-postgres-bootstrap-databases` и запускает `platform-event-log`
  migrations;
- для `web` ring не запускает PostgreSQL foundation и backend migrations;
- для `web-public` ring не запускает registry, Kaniko, PostgreSQL и backend
  migrations; он применяет только public web/webhook-contour;
- запускает migrations, где они есть, и deployments для сервисов выбранного
  кольца;
- ждёт rollout и проверяет `/health/readyz` через локальный `kubectl
  port-forward`.

Секреты, DSN, токены, домены, адреса registry и kubeconfig не печатаются.
Повторный запуск удаляет только одноразовые build/migration `Job`, чтобы
Kubernetes не упёрся в immutable job spec, и не удаляет кластер, namespace,
registry PVC, PostgreSQL PVC или базы.

Если образы уже собраны и нужно только повторить apply/migrations/rollout для
выбранного кольца:

```bash
bash bootstrap/host/deploy_backend_ring.sh \
  --env-file bootstrap/host/config.env \
  --ring second \
  --skip-build
```

## Проверка registry и Kaniko

После установки foundation:

```bash
KODEX_SMOKE_ENV_FILE=bootstrap/host/config.env \
  bash bootstrap/host/smoke_registry_kaniko.sh
```

Скрипт:

- рендерит `deploy/base/bootstrap-foundation/**` и `deploy/base/bootstrap-builder-smoke/**`;
- ждёт readiness `kodex-registry`;
- зеркалирует внешний тестовый образ во внутренний registry через `crane`;
- запускает pull-smoke из внутреннего registry;
- собирает минимальный scratch-образ через Kaniko и пушит его во внутренний registry.

Docker daemon не требуется.

## Проверка backend после deploy

Когда первое backend-кольцо применено, можно запускать обвязку проверки. Она
повторяет идемпотентный deploy первого кольца с `--skip-build`: проверяет
registry, Kubernetes `Secret`, PostgreSQL, migrations, rollout и
`/health/readyz`, не перезаписывая секреты значениями из env.

```bash
KODEX_SMOKE_ENV_FILE=bootstrap/host/config.env \
  bash bootstrap/host/smoke_backend_contour.sh
```

Эта обвязка проверяет только первое кольцо. Второе кольцо проверяется через
`deploy_backend_ring.sh --ring second` и readiness, migration jobs, rollout
status. `staff-gateway` проверяется через `deploy_backend_ring.sh --ring staff`,
readiness, rollout status и OpenAPI endpoint. `platform-mcp-server` проверяется
через `deploy_backend_ring.sh --ring mcp`, readiness, rollout status, `/metrics`
и наличие MCP endpoint `/mcp`. `web-console` проверяется через
`deploy_backend_ring.sh --ring web`, readiness, rollout status и HTTP `GET /`
через локальный `kubectl port-forward`. Публичный web-contour проверяется через
`deploy_backend_ring.sh --ring web-public`, readiness `kodex-public-ingress`,
готовность `cert-manager`, `ClusterIssuer`, `Certificate`, rollout
`oauth2-proxy`, HTTPS/OAuth redirect и HMAC-only GitHub webhook ingress.

Доменные и end-to-end проверки не добавляются как shell smoke scripts в
`scripts/**`. Их целевой формат — Go tests или отдельный Go integration runner;
shell остаётся только тонкой обвязкой установки, deploy или запуска Go tooling.

## Операционная приёмка после deploy

После применения нужных rings и перед ручной проверкой UI владелец или агент
запускает read-only приёмку текущего кластера:

```bash
go run ./cmd/bootstrap-operational-acceptance \
  --env-file bootstrap/host/config.env
```

Команда не меняет кластер: не выполняет `kubectl apply`, не запускает jobs, не
собирает и не пушит образы. Она проверяет:

- `services.yaml` и builder/registry image refs через `libs/go/stackinventory`;
- доступность Kubernetes API `/readyz`;
- готовность deployments, PostgreSQL statefulset и обязательных migration jobs;
- отсутствие активных или failed backend build jobs;
- наличие Service объектов и сохранение внутренних сервисов как `ClusterIP`;
- наличие обязательных ключей в `kodex-postgres`, `kodex-platform-runtime` и
  `kodex-web-oauth2-proxy` без вывода значений;
- доступность локального registry endpoint на loopback;
- операторскую HTTP-поверхность через локальный `kubectl port-forward`:
  `staff-gateway` OpenAPI, `web-console` root и `oauth2-proxy` ping;
- что public web `Ingress` ведёт только на `oauth2-proxy`, а не напрямую на
  `web-console`;
- что public GitHub webhook `Ingress` ведёт только на `integration-gateway` по
  точному path `/v1/provider-webhooks/github` и не попадает в `oauth2-proxy`;
- готовность `Certificate` и `ClusterIssuer`;
- что публичный `web-console` root редиректит на GitHub OAuth без вывода
  query/state;
- что неподписанный запрос к публичному GitHub webhook endpoint получает
  `401/signature_invalid`, а не OAuth redirect.

Если команда завершилась строкой `OK: operational acceptance completed`,
платформа готова к ручной UI-проверке владельца через
`https://platform.kodex.works`. Интерактивный вход owner-аккаунтом не входит в
автоматическую приёмку: агент не должен печатать cookie, OAuth `state`, токены
или содержимое callback.

## Регистрация GitHub webhook

Публичный endpoint для репозитория `codex-k8s/kodex`:

```text
https://platform.kodex.works/v1/provider-webhooks/github
```

Этот endpoint не находится за OAuth. Единственная защита входа — GitHub
`X-Hub-Signature-256`, body limit, route rate limit/backpressure и разрешённый
`provider_slug=github`. Значение `KODEX_GITHUB_WEBHOOK_SECRET` хранится в
Kubernetes `Secret` `kodex-platform-runtime` и не попадает в `ConfigMap`,
документы, Issue, PR или логи.

Регистрацию webhook выполняет владелец или бот с достаточными правами GitHub
после применения `--ring web-public`. Секрет не выводить и не копировать в
документы; команда ниже передаёт его только через переменную процесса:

```bash
set -euo pipefail
set -a
source bootstrap/host/config.env
set +a

WEBHOOK_SECRET="$(
  kubectl -n "$KODEX_PRODUCTION_NAMESPACE" \
    get secret kodex-platform-runtime \
    -o jsonpath='{.data.KODEX_GITHUB_WEBHOOK_SECRET}' | base64 -d
)"

GH_TOKEN="${KODEX_GIT_BOT_TOKEN:-${KODEX_GITHUB_PAT:-}}" \
  gh api repos/codex-k8s/kodex/hooks \
    --method POST \
    -f name=web \
    -f active=true \
    -f events[]=push \
    -f events[]=pull_request \
    -f config[url]=https://platform.kodex.works/v1/provider-webhooks/github \
    -f config[content_type]=json \
    -f config[insecure_ssl]=0 \
    -f config[secret]="$WEBHOOK_SECRET"

unset WEBHOOK_SECRET
```

GitHub repository webhook не фильтрует `push` по ветке на уровне регистрации.
`provider-hub` получает safe envelope и оставляет бизнес-фильтрацию `main` в
owner-контуре. Лишние GitHub events не включать.

Чтобы проверить backend wrapper без запуска проверки registry и deploy:

```bash
KODEX_BACKEND_SMOKE_DRY_RUN=true \
KODEX_SMOKE_ENV_FILE=bootstrap/host/config.env \
  bash bootstrap/host/smoke_backend_contour.sh
```

Этот режим вызывает план backend deploy и завершает работу до любых изменений
кластера.
