Bootstrap¶
deploy/bootstrap.py — единый идемпотентный скрипт первичной инициализации
платформы: он заводит tenant в IAM и Control Plane, первого человека-
администратора, его Platform Access Token, проект и workspace, каталог типов
задач, service accounts ядра и опциональных сервисов, агентов из реестра и
настройки policy-service. Статья разбирает каждый шаг по коду скрипта: какие
вызовы API он делает, что сохраняет и что делать при сбое.
Запуск¶
make bootstrap # deploy/bootstrap.py --env .env
make bootstrap ARGS='--operator "Alice Operator"'
make bootstrap ARGS=--policy # профиль policy обязателен
make bootstrap запускает скрипт через
uv run --no-project --quiet --with pyyaml --with jsonschema python3, если uv
установлен, и системным python3 — если нет. Там, где make нет (например,
на сервере стенда), скрипт вызывают напрямую:
uv run --no-project --with pyyaml --with jsonschema python3 deploy/bootstrap.py --env .env
# или системным python3, если в нём есть PyYAML и jsonschema:
python3 deploy/bootstrap.py --env .env
Скрипт работает с хоста, а не из контейнера: к Control Plane и IAM он ходит
по портам 127.0.0.1 (CP_HOST_PORT, IAM_HOST_PORT, для шага 7 —
POL_HOST_PORT), значения читает из .env.
Параметры¶
| Флаг | По умолчанию | Смысл |
|---|---|---|
--env |
.env |
файл окружения (путь относительно корня суперпроекта) |
--name |
COMPOSE_PROJECT_NAME |
имя окружения: файл состояния deploy/state/<name>.json |
--operator |
служебное имя в скрипте | отображаемое имя первого человека-администратора; задайте своё |
--tenant-slug |
COMPOSE_PROJECT_NAME |
slug tenant в IAM и Control Plane (он же ключ шаблона проекта и slug workspace) |
--pat-ttl |
15552000 (180 дней) |
срок жизни выпускаемых PAT, секунды (не больше IAM_PAT_MAX_TTL_SECONDS, 365 дней) |
--secrets-dir |
secrets |
куда писать PAT и env-файлы service accounts |
--packages |
deploy/packages.yaml |
файл установки каталога (kind: Installation) — шаг 5b |
--policy |
выкл. | профиль policy обязателен: ждать policy-service до 30 с и падать, если его нет — шаг 7 |
Зависимости¶
Скрипт использует только стандартную библиотеку Python для HTTP, но шаг 5b
импортирует tools/cp_packages.py, которому нужны PyYAML и jsonschema.
make bootstrap при установленном uv подключает их сам; без uv они нужны в
системном Python.
Доменные валидаторы Control Plane cp_packages берёт из сабмодуля
control-plane/src; если они не импортируются, выдаётся предупреждение и
проверяется только схема формата.
Идемпотентность и состояние¶
Все созданные идентификаторы (не секреты) пишутся в
deploy/state/<name>.json после каждого шага. Повторный запуск читает этот
файл и пропускает сделанное: tenant, principals, шаблон и проект не создаются
заново. Некоторые действия выполняются при каждом запуске намеренно — они
приводят стенд к описанию:
PATCHпотолков scopes всех audiences по реестру скрипта;- установка пакетов каталога (новая версия типа — только при расхождении);
- публикация описаний сервисов платформы и привязка их личностей в ядре (шаги 5c, 5d).
Секреты в файл состояния не попадают: PAT и client secrets пишутся только в
secrets/ с правами 0600, в stdout печатаются лишь префиксы и id.
Состояние и данные должны совпадать
Файл состояния описывает конкретные базы. Если удалить volumes (полный
сброс), выполните make reset-state: цель переносит
deploy/state/<name>.json и выданные bootstrap credentials
(secrets/harness-pat, secrets/*-iam.env, secrets/policy-service-cp.env)
в secrets/stale-<время>/; ключи подписи и .env
не трогает. Если этого не сделать, bootstrap остановится сразу после
ожидания сервисов: IAM tenant из файла состояния не найден (404
tenant_not_found), и скрипт подскажет make reset-state. Обратная ситуация — базы
живы, а файл состояния потерян — даёт 409 already_bootstrapped на шаге 3:
восстановите файл из резервной копии.
Пример deploy/state/taimen.json после первого прогона:
{
"iamTenantId": "<tenant-id>",
"iamAudiences": ["control-plane", "entitlement-service", "fleet", "human-harness", "iam", "memory-service", "notification-service", "policy-service"],
"iamOperatorPrincipalId": "<iam-principal-id>",
"cpServiceAccountClientId": "<client-id>",
"cpServiceAccountPrincipalId": "<iam-principal-id>",
"cpServiceAccountCeiling": ["entitlement:check-on-behalf", "memory:on-behalf", "…"],
"cpTenantId": "<tenant-id>",
"cpOperatorPrincipalId": "<cp-principal-id>",
"cpLegacyAdminKeyId": "<api-key-id>",
"cpOperatorBindingId": "<binding-id>",
"operatorPatPrefix": "<prefix>",
"operatorPatExpiresAt": "<дата>",
"templateId": "<template-id>",
"projectId": "<project-id>",
"workspaceId": "<workspace-id>",
"catalog": { "TaskType/coding-task": { "id": "…", "version": 1 }, "…": {} },
"cpLegacyAdminKeyRevoked": true
}
Шаги¶
flowchart TB
S1[1. Ожидание CP и IAM] --> S2[2. IAM: tenant, audiences,<br/>human principal оператора]
S2 --> S2a[2a. Service account ядра<br/>→ secrets/control-plane-iam.env]
S2a --> S3[3. CP bootstrap: tenant с тем же id,<br/>admin principal, binding оператора]
S3 --> S4[4. PAT оператора<br/>→ secrets/harness-pat, обмен]
S4 --> S5[5. Шаблон проекта, проект, workspace]
S5 --> S5b[5b. Пакеты каталога]
S5b --> S5c[5c, 5d. Сервисы платформы: SA по описанию,<br/>личность в ядре → secrets/*-iam.env]
S5c --> R[Отзыв legacy api-key]
R --> S7{POL_BOOTSTRAP_TOKEN задан<br/>и policy отвечает?<br/>с --policy — ждать до 30 с}
S7 -- да --> P7[7. policy-service]
S7 -- нет --> END[готово]
S7 -. нет и --policy .-> F[ошибка: не дождался …/healthz]
P7 --> END
Нумерация в выводе скрипта историческая: прежние шаги 5a (process-runtime) и 6 (агенты из реестра) выведены, номера остальных сохранены.
1. Ожидание сервисов¶
Опрос GET /health/ready Control Plane и GET /healthz IAM раз в 2 секунды,
до 180 секунд. Не дождался — SystemExit("не дождался …").
Если в файле состояния уже есть iamTenantId, скрипт проверяет, что tenant
существует в IAM (GET /api/v1/tenants/{t}/audiences с bootstrap-токеном).
Ответ 404 означает, что volumes сброшены, а state остался; скрипт
останавливается с сообщением … ссылается на IAM tenant …, которого нет в IAM
(volumes сброшены?). Запуститеmake reset-stateи повторите bootstrap.
2. IAM: tenant, audiences, оператор¶
Все вызовы — с заголовком X-IAM-Bootstrap-Token: ${IAM_BOOTSTRAP_TOKEN}.
POST /api/v1/tenants{"slug": <slug>, "name": <slug>}→iamTenantId.-
Для каждого audience реестра —
POST /api/v1/tenants/{t}/audiences{"key", "allowedScopes"}(ответ409— уже есть, не ошибка), затем всегдаPATCH /api/v1/tenants/{t}/audiences/{key}{"allowedScopes"}— потолок audience растёт вместе с сервисом.Audience allowedScopescontrol-planecontrol-plane:read,control-plane:write,control-plane:adminmemory-servicememory:read,memory:write,memory:pii,memory:tenants,memory:on-behalf,memory:serviceentitlement-serviceentitlement:check-on-behalfpolicy-servicepolicy:check,policy:check-on-behalf,policy:admin -
POST /api/v1/tenants/{t}/principals{"kind": "human", "displayName": <--operator>}→iamOperatorPrincipalId.
Если IAM_TENANT_ID в .env не совпадает с созданным tenant, скрипт печатает
!! впишите в .env: IAM_TENANT_ID=… — сделайте это (переменная нужна шлюзу
platform-api, fleet-controller и коннекторам).
2a. Service account Control Plane¶
POST /api/v1/tenants/{t}/service-accounts:
{
"displayName": "Taimen Control Plane",
"audiences": ["memory-service", "entitlement-service", "policy-service"],
"scopeCeiling": ["memory:read", "memory:write", "memory:tenants", "memory:on-behalf",
"memory:service", "entitlement:check-on-behalf",
"policy:check", "policy:check-on-behalf"]
}
Ответ (clientId, clientSecret) записывается в
secrets/control-plane-iam.env как CP_IAM_CLIENT_ID / CP_IAM_CLIENT_SECRET.
Этот файл подключён env_file к трём процессам ядра; с ним Control Plane ходит
в память токеном IAM (CP_CONTEXT_AUTH=auto).
Если потолок в коде скрипта изменился по сравнению с сохранённым
(cpServiceAccountCeiling), service account перевыпускается, а прежний
отзывается (POST …/service-accounts/{clientId}:revoke): изменения потолка у
service account нет.
Перезапуск ядра
После выпуска или перевыпуска файла выполните
docker compose up -d control-plane-api control-plane-worker context-adapter —
env_file читается при создании контейнера.
3. Control Plane: tenant, администратор, binding¶
POST /api/v1/bootstrap с Authorization: Bearer ${CP_BOOTSTRAP_TOKEN}:
{
"tenantSlug": "taimen",
"tenantName": "Taimen",
"adminDisplayName": "Alice Operator",
"tenantId": "<iam-tenant-id>",
"iamBinding": {
"issuer": "http://taimen.localhost/iam",
"iamTenantId": "<iam-tenant-id>",
"iamPrincipalId": "<iam-principal-id>"
}
}
Одной транзакцией Control Plane создаёт:
- tenant с тем же UUID, что у tenant IAM, — единый идентификатор организации;
- principal
humanс правомadmin; - legacy API-ключ администратора (возвращается в ответе, скрипт его позже отзывает);
- binding оператора
(issuer, iamPrincipalId)со всеми правами по имени — не толькоadmin, чтобы токен с потолком read+write не сузил binding до нуля.
Эндпоинт работает один раз: если в базе уже есть tenant, ответ
409 already_bootstrapped. Issuer берётся из TAIMEN_PUBLIC_URL — поэтому
публичный адрес нужно выбрать до bootstrap.
4. PAT оператора¶
POST /api/v1/tenants/{t}/principals/{p}/authentication-contexts{"issuer", "acr": "bootstrap", "amr": ["bootstrap-script"]}— свежий контекст аутентификации (IAM выпускает PAT человеку, только если контекст не старше 300 секунд).-
POST /api/v1/tenants/{t}/principals/{p}/platform-access-tokensсIdempotency-Key: -
Токен пишется в
secrets/harness-pat(0600); префикс и срок — в состояние. - Проверочный обмен
POST /api/v1/platform-access-tokens:exchangeна токен audiencecontrol-planeсо всеми тремя scopes. Этим токеном выполняются шаги 5–7.
Если secrets/harness-pat уже существует, выпуск пропускается и используется
файл.
5. Шаблон проекта, проект и workspace¶
От имени оператора (Bearer-токен из шага 4):
POST /api/v1/project-templates{"key": <slug>, "displayName": …}→templateId.-
POST /api/v1/projects:{ "workspaceSlug": "taimen", "workspaceName": "Taimen", "workspaceTypeKey": "generic", "templateId": "<template-id>", "ownerPrincipalId": "<cp-principal-id>" }Control Plane создаёт workspace системного типа
genericи прикрепляет к нему Project Profile →projectId,workspaceId.
5b. Каталог из пакетов¶
Файл установки по умолчанию — deploy/packages.yaml:
Ядро доменно-нейтрально: по умолчанию инсталляция знает только системный тип
task. Типы предметной области ставятся своими пакетами — например, цикл
саморазработки платформы (selfdev, sdd).
cp_packages.apply сначала проверяет пакеты (JSON Schema формата, доменные
валидаторы Control Plane, замкнутость ссылок), затем приводит tenant к ним в
порядке WorkspaceType, Capability, Role, Skill, ArtifactType, TaskType,
Agent, ProjectTemplate, WorkRule, NotificationRule (подробно — в Пакетах
каталога):
| Вид объекта | Как применяется |
|---|---|
TaskType, ProjectTemplate |
версии неизменяемы: если активная версия расходится с пакетом — публикуется новая, прежние активные → deprecated; совпадает — «без изменений» |
WorkspaceType, Role |
создаются или приводятся PATCH |
Capability |
только создание |
Skill |
контракт неизменяем, меняется поднятием версии; описание и config — PATCH |
retire в файле установки |
указанные типы и шаблоны → deprecated |
Строки ${NAME} в spec подставляются из .env и окружения процесса. Правила
уведомлений (NotificationRule) применяются к сервису уведомлений, если задан
NOTIFICATION_SERVICE_URL, иначе пропускаются с предупреждением. Результат — карта
catalog в состоянии. Подробно —
Пакеты каталога.
5c, 5d. Сервисы платформы¶
Сервисы, которые ходят в ядро по client credentials IAM, описаны агентами без
размещения (placement: none, identity.kind: service) в пакете каталога
platform-services. Для каждого — notification-service (5c) и
fleet-controller (5d) — шаг выполняется всегда, даже если профиль сервиса не
поднят (затрагивает только IAM и Control Plane):
- Service account IAM с audiences и потолком из
identity.iamописания →secrets/notification-iam.env(NS_SERVICE_CLIENT_ID,NS_SERVICE_CLIENT_SECRET) илиsecrets/fleet-iam.env(FLEET_CLIENT_ID,FLEET_CLIENT_SECRET). Если потолок в описании изменился, учётка перевыпускается, прежняя отзывается. POST /api/v1/agents— публикация описания в ядре.PUT /api/v1/agents/{key}/identity— привязка IAM principal учётки; principal ядра и связку с правами из описания выводит ядро.
После выпуска файла скрипт напоминает пересоздать сервис
(docker compose --profile notify up -d notification-service,
docker compose --profile fleet up -d fleet-controller).
Затем скрипт отзывает legacy API-ключ администратора из шага 3
(POST /api/v1/api-keys/{id}:revoke) — стенд работает только через IAM.
6. Агенты (шага больше нет)¶
Прежний шаг 6 заводил агентов из реестра --agents. Теперь исполнителей
описывают пакеты каталога, а поднимает платформа — см.
Декларативные агенты. Нумерация шагов
сохранена.
7. policy-service (при непустом POL_BOOTSTRAP_TOKEN и поднятом профиле)¶
Шаг выполняется, только если POL_BOOTSTRAP_TOKEN непуст и policy-service
отвечает на GET /healthz. Без флага --policy проверка одна, без ожидания:
если сервис не отвечает, скрипт печатает
7. policy-service не отвечает (профиль policy не поднят) — шаг пропущен; обязательным его делает --policy
и завершается успешно. С --policy (make bootstrap ARGS=--policy) скрипт
ждёт /healthz до 30 секунд и падает не дождался …/healthz — всё, что сделано
на предыдущих шагах, уже сохранено. При пустом POL_BOOTSTRAP_TOKEN шаг не
выполняется и не упоминается в выводе.
При поднятом сервисе (заголовок X-Policy-Bootstrap-Token):
- регистрирует каталоги действий всех компонентов (
*/authz/catalog.yaml) —POST /api/v1/catalogs/{service}; - публикует модель —
POST /api/v1/model-versions:publish; - создаёт store tenant, системную роль
tenant-adminсо всеми действиями и назначает её оператору на весь tenant; - заводит service account памяти для вызова PDP →
secrets/memory-service-iam.env; - заводит service account воркера проекции (audience
control-plane,control-plane:read) →secrets/policy-service-cp.env, principal в Control Plane и binding сevents.read.
Профиль policy экспериментальный — см.
Policy Service.
Итог¶
В конце скрипт печатает путь к файлу состояния и подсказку для клиента:
готово: deploy/state/taimen.json
credential для MCP-плагина/CLI: ~/.config/iam/credentials.json, ключ http://taimen.localhost/iam|<tenant-id>|<iam-principal-id> → содержимое secrets/harness-pat
Как этим воспользоваться — в Первой задаче.
Файлы, которые создаёт bootstrap¶
| Файл | Шаг | Содержимое | Кто читает |
|---|---|---|---|
deploy/state/<name>.json |
все | идентификаторы, не секреты | сам bootstrap |
secrets/control-plane-iam.env |
2a | client credentials ядра | процессы Control Plane |
secrets/harness-pat |
4 | PAT оператора | человек: CLI, MCP-плагин, curl |
secrets/notification-iam.env |
5c | client credentials сервиса уведомлений | notification-service |
secrets/fleet-iam.env |
5d | client credentials контроллера fleet | fleet-controller |
secrets/memory-service-iam.env |
7 | client credentials памяти | memory-service |
secrets/policy-service-cp.env |
7 | токен и client credentials проекции | policy-worker |
Повторный запуск и перевыпуск¶
| Задача | Действие |
|---|---|
Обновить каталог после изменения packages/ |
make bootstrap — изменится только то, что расходится |
| Поставить другой набор пакетов | make bootstrap ARGS="--packages deploy/<env>/packages.yaml" |
| Начать заново после сброса volumes | make reset-state, затем make bootstrap |
| Перевыпустить PAT оператора (истекает) | удалить secrets/harness-pat и запустить bootstrap: новый выпуск со свежим контекстом аутентификации; старый PAT отзовите отдельно |
| Перевыпустить service account ядра | удалить secrets/control-plane-iam.env и запустить bootstrap, затем перезапустить ядро |
| Добавить исполнителей | описать агента в пакете и разместить через fleet — Декларативные агенты |
Типичные ошибки¶
| Сообщение | Причина | Решение |
|---|---|---|
не дождался http://127.0.0.1:18000/health/ready |
ядро не поднялось или порт другой | make ps, make logs svc=control-plane-api; проверить CP_HOST_PORT |
POST /api/v1/tenants: HTTP 401 |
IAM_BOOTSTRAP_TOKEN в .env не совпадает с тем, с которым запущен iam-service |
после правки .env пересоздать контейнер: docker compose up -d iam-service |
POST /api/v1/bootstrap: HTTP 409 … already_bootstrapped |
Control Plane уже инициализирован, файла состояния нет | восстановить deploy/state/<name>.json |
POST /api/v1/bootstrap: HTTP 403 … bootstrap_disabled |
пустой CP_BOOTSTRAP_TOKEN |
задать значение, пересоздать control-plane-api |
нужен PyYAML / нужен jsonschema |
uv не установлен, у системного Python нет зависимостей | установить uv или pip install pyyaml jsonschema; при прямом вызове — uv run --no-project --with pyyaml --with jsonschema python3 deploy/bootstrap.py … |
… ссылается на IAM tenant …, которого нет в IAM (volumes сброшены?) |
volumes сброшены, файл состояния остался | make reset-state и повторить bootstrap |
пакеты не прошли проверку: … |
ошибка в YAML пакета | make packages-check, исправить пакет |
platform-access-tokens:exchange: HTTP 500 |
IAM не может прочитать ключ подписи | на Linux chown 10001:10001 secrets/iam-signing.pem, перезапустить iam-service |
не дождался http://127.0.0.1:18040/healthz |
запуск с --policy, профиль policy не поднят |
поднять профиль policy или запускать без --policy (шаг 7 пропустится) |
<slug>: агенту нельзя выдавать admin / approvals.decide |
реестр агентов выдаёт запрещённое право | убрать право из реестра |